diff --git a/.env.development b/.env.development index b8d33d1..ea8c987 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ -VITE_API_BASE_URL=/mocks +VITE_API_BASE_URL= VITE_API_MOCKING_ENABLED=true diff --git a/.prettierignore b/.prettierignore index 9d2e07c..dc4e889 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ src/components/ui/* +src/generated/* diff --git a/eslint.config.js b/eslint.config.js index bd06d9a..c36899f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -20,6 +20,7 @@ export default tseslint.config( 'dist', '.storybook', 'public/mockServiceWorker.js', + 'src/generated', ] }, { extends: [ diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts new file mode 100644 index 0000000..2d5aa94 --- /dev/null +++ b/openapi-ts.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from '@hey-api/openapi-ts'; + +export default defineConfig({ + input: 'opt/wildcat/__dev_openapi.json', + output: { + format: 'prettier', + lint: 'eslint', + path: './src/generated/client', + }, + plugins: [ + '@hey-api/client-fetch', + '@hey-api/sdk', + /* '@hey-api/schemas', { + enums: 'javascript', + name: '@hey-api/typescript', + },*/ + '@tanstack/react-query', + ], +}) diff --git a/opt/wildcat/__dev_openapi.json b/opt/wildcat/__dev_openapi.json new file mode 100644 index 0000000..61326d5 --- /dev/null +++ b/opt/wildcat/__dev_openapi.json @@ -0,0 +1,752 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "wildcat", + "description": "", + "license": { + "name": "" + }, + "version": "0.1.0" + }, + "paths": { + "/v1/admin/credit/quote/{id}": { + "get": { + "tags": [ + "crate::credit::admin" + ], + "operationId": "admin_lookup_quote", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The quote id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Succesful response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InfoReply" + } + } + } + }, + "404": { + "description": "Quote id not found" + } + } + }, + "post": { + "tags": [ + "crate::credit::admin" + ], + "operationId": "resolve_quote", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The quote id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResolveRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Succesful response" + } + } + } + }, + "/v1/admin/credit/quote/accepted": { + "get": { + "tags": [ + "crate::credit::admin" + ], + "operationId": "list_accepted_quotes", + "parameters": [ + { + "name": "since", + "in": "query", + "description": "only accepted quotes younger than `since`", + "required": false, + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Succesful response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListReply" + } + } + } + } + } + } + }, + "/v1/admin/credit/quote/pending": { + "get": { + "tags": [ + "crate::credit::admin" + ], + "summary": "--------------------------- List quotes", + "operationId": "list_pending_quotes", + "parameters": [ + { + "name": "since", + "in": "query", + "description": "only quote requests younger than `since`", + "required": false, + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Succesful response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListReply" + } + } + } + } + } + } + }, + "/v1/credit/mint/quote": { + "post": { + "tags": [ + "crate::credit::web" + ], + "summary": "--------------------------- Enquire mint quote", + "operationId": "enquire_quote", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnquireRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Quote request admitted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnquireReply" + } + } + } + }, + "404": { + "description": "Quote request not accepted" + } + } + } + }, + "/v1/credit/mint/quote/{id}": { + "get": { + "tags": [ + "crate::credit::web" + ], + "operationId": "lookup_quote", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The quote id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Succesful response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatusReply" + } + } + } + }, + "404": { + "description": "Quote id not found" + } + } + } + }, + "/v1/credit/quote/{id}": { + "post": { + "tags": [ + "crate::credit::web" + ], + "operationId": "resolve_offer", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The quote id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resolve" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Succesful response" + }, + "404": { + "description": "Quote not found" + }, + "409": { + "description": "Quote already resolved" + } + } + } + } + }, + "components": { + "schemas": { + "Id": { + "type": "string", + "format": "uuid", + "description": "Manually added - should be replaced with generated one" + }, + "BlindSignatureDleq": { + "type": "string", + "description": "Manually added - should be replaced with generated one" + }, + "Witness": { + "type": "string", + "description": "Manually added - should be replaced with generated one" + }, + "TStamp": { + "type": "integer", + "format": "int64", + "description": "Manually added - should be replaced with generated one", + "minimum": 0 + }, + "Resolve": { + "type": "integer", + "format": "int64", + "description": "Manually added - should be replaced with generated one", + "minimum": 0 + }, + "Amount": { + "type": "integer", + "format": "int64", + "description": "Amount can be any unit", + "minimum": 0 + }, + "BlindSignature": { + "type": "object", + "description": "Blind Signature (also called `promise`)", + "required": [ + "amount", + "id", + "C_" + ], + "properties": { + "C_": { + "type": "string", + "description": "Blinded signature (C_)\n\nThe blinded signature on the secret message `B_` of [BlindedMessage]." + }, + "amount": { + "$ref": "#/components/schemas/Amount" + }, + "dleq": { + "allOf": [ + { + "$ref": "#/components/schemas/BlindSignatureDleq" + } + ], + "nullable": true + }, + "id": { + "$ref": "#/components/schemas/Id" + } + } + }, + "BlindedMessage": { + "type": "object", + "description": "Blinded Message (also called `output`)", + "required": [ + "amount", + "id", + "B_" + ], + "properties": { + "B_": { + "type": "string", + "description": "Blinded secret message (B_)\n\nThe blinded secret message generated by the sender." + }, + "amount": { + "$ref": "#/components/schemas/Amount" + }, + "id": { + "$ref": "#/components/schemas/Id" + }, + "witness": { + "allOf": [ + { + "$ref": "#/components/schemas/Witness" + } + ], + "nullable": true + } + } + }, + "EnquireReply": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + } + } + }, + "EnquireRequest": { + "type": "object", + "description": "--------------------------- Enquire mint quote", + "required": [ + "bill", + "node", + "outputs" + ], + "properties": { + "bill": { + "type": "string" + }, + "node": { + "type": "string" + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlindedMessage" + } + } + } + }, + "InfoReply": { + "oneOf": [ + { + "type": "object", + "required": [ + "id", + "bill", + "endorser", + "submitted", + "suggested_expiration", + "status" + ], + "properties": { + "bill": { + "type": "string" + }, + "endorser": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "pending" + ] + }, + "submitted": { + "type": "string", + "format": "date-time" + }, + "suggested_expiration": { + "type": "string", + "format": "date-time" + } + } + }, + { + "type": "object", + "required": [ + "id", + "bill", + "endorser", + "ttl", + "signatures", + "status" + ], + "properties": { + "bill": { + "type": "string" + }, + "endorser": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "signatures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlindSignature" + } + }, + "status": { + "type": "string", + "enum": [ + "offered" + ] + }, + "ttl": { + "type": "string", + "format": "date-time" + } + } + }, + { + "type": "object", + "required": [ + "id", + "bill", + "endorser", + "status" + ], + "properties": { + "bill": { + "type": "string" + }, + "endorser": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "denied" + ] + } + } + }, + { + "type": "object", + "required": [ + "id", + "bill", + "endorser", + "signatures", + "status" + ], + "properties": { + "bill": { + "type": "string" + }, + "endorser": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "signatures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlindSignature" + } + }, + "status": { + "type": "string", + "enum": [ + "accepted" + ] + } + } + }, + { + "type": "object", + "required": [ + "id", + "bill", + "endorser", + "tstamp", + "status" + ], + "properties": { + "bill": { + "type": "string" + }, + "endorser": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "rejected" + ] + }, + "tstamp": { + "$ref": "#/components/schemas/TStamp" + } + } + } + ], + "description": "--------------------------- Quote info request", + "discriminator": { + "propertyName": "status" + } + }, + "ListReply": { + "type": "object", + "description": "--------------------------- List quotes", + "required": [ + "quotes" + ], + "properties": { + "quotes": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "ResolveOffer": { + "oneOf": [ + { + "type": "object", + "required": [ + "action" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "reject" + ] + } + } + }, + { + "type": "object", + "required": [ + "action" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "accept" + ] + } + } + } + ], + "description": "--------------------------- Resolve quote" + }, + "ResolveRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "action" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "deny" + ] + } + } + }, + { + "type": "object", + "required": [ + "discount", + "action" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "offer" + ] + }, + "discount": { + "type": "string" + }, + "ttl": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + ], + "description": "--------------------------- Resolve quote request", + "discriminator": { + "propertyName": "action" + } + }, + "StatusReply": { + "oneOf": [ + { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "pending" + ] + } + } + }, + { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "denied" + ] + } + } + }, + { + "type": "object", + "required": [ + "signatures", + "expiration_date", + "status" + ], + "properties": { + "expiration_date": { + "$ref": "#/components/schemas/TStamp" + }, + "signatures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlindSignature" + } + }, + "status": { + "type": "string", + "enum": [ + "offered" + ] + } + } + }, + { + "type": "object", + "required": [ + "signatures", + "status" + ], + "properties": { + "signatures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlindSignature" + } + }, + "status": { + "type": "string", + "enum": [ + "accepted" + ] + } + } + }, + { + "type": "object", + "required": [ + "tstamp", + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "rejected" + ] + }, + "tstamp": { + "$ref": "#/components/schemas/TStamp" + } + } + } + ], + "description": "--------------------------- Look up quote", + "discriminator": { + "propertyName": "status" + } + } + } + } +} \ No newline at end of file diff --git a/opt/wildcat/openapi.json b/opt/wildcat/openapi.json new file mode 100644 index 0000000..4bf9bf3 --- /dev/null +++ b/opt/wildcat/openapi.json @@ -0,0 +1 @@ +{"openapi":"3.0.3","info":{"title":"wildcat","description":"","license":{"name":""},"version":"0.1.0"},"paths":{"/v1/admin/credit/quote/:id":{"get":{"tags":["crate::credit::admin"],"operationId":"lookup_quote","parameters":[{"name":"id","in":"path","description":"The quote id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Succesful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InfoReply"}}}},"404":{"description":"Quote id not found"}}},"post":{"tags":["crate::credit::admin"],"operationId":"resolve_quote","parameters":[{"name":"id","in":"path","description":"The quote id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveRequest"}}},"required":true},"responses":{"200":{"description":"Succesful response"}}}},"/v1/admin/credit/quote/accepted":{"get":{"tags":["crate::credit::admin"],"operationId":"list_accepted_quotes","parameters":[{"name":"since","in":"query","description":"only accepted quotes younger than `since`","required":false,"schema":{"type":"string","format":"date-time","nullable":true}}],"responses":{"200":{"description":"Succesful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListReply"}}}}}}},"/v1/admin/credit/quote/pending":{"get":{"tags":["crate::credit::admin"],"summary":"--------------------------- List quotes","operationId":"list_pending_quotes","parameters":[{"name":"since","in":"query","description":"only quote requests younger than `since`","required":false,"schema":{"type":"string","format":"date-time","nullable":true}}],"responses":{"200":{"description":"Succesful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListReply"}}}}}}},"/v1/credit/mint/quote":{"post":{"tags":["crate::credit::web"],"summary":"--------------------------- Enquire mint quote","operationId":"enquire_quote","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnquireRequest"}}},"required":true},"responses":{"200":{"description":"Quote request admitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnquireReply"}}}},"404":{"description":"Quote request not accepted"}}}},"/v1/credit/mint/quote/:id":{"get":{"tags":["crate::credit::web"],"operationId":"lookup_quote","parameters":[{"name":"id","in":"path","description":"The quote id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Succesful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusReply"}}}},"404":{"description":"Quote id not found"}}}},"/v1/credit/quote/:id":{"post":{"tags":["crate::credit::web"],"operationId":"resolve_offer","parameters":[{"name":"id","in":"path","description":"The quote id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Resolve"}}},"required":true},"responses":{"200":{"description":"Succesful response"},"404":{"description":"Quote not found"},"409":{"description":"Quote already resolved"}}}}},"components":{"schemas":{"Amount":{"type":"integer","format":"int64","description":"Amount can be any unit","minimum":0},"BlindSignature":{"type":"object","description":"Blind Signature (also called `promise`)","required":["amount","id","C_"],"properties":{"C_":{"type":"string","description":"Blinded signature (C_)\n\nThe blinded signature on the secret message `B_` of [BlindedMessage]."},"amount":{"$ref":"#/components/schemas/Amount"},"dleq":{"allOf":[{"$ref":"#/components/schemas/BlindSignatureDleq"}],"nullable":true},"id":{"$ref":"#/components/schemas/Id"}}},"BlindedMessage":{"type":"object","description":"Blinded Message (also called `output`)","required":["amount","id","B_"],"properties":{"B_":{"type":"string","description":"Blinded secret message (B_)\n\nThe blinded secret message generated by the sender."},"amount":{"$ref":"#/components/schemas/Amount"},"id":{"$ref":"#/components/schemas/Id"},"witness":{"allOf":[{"$ref":"#/components/schemas/Witness"}],"nullable":true}}},"EnquireReply":{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"uuid"}}},"EnquireRequest":{"type":"object","description":"--------------------------- Enquire mint quote","required":["bill","node","outputs"],"properties":{"bill":{"type":"string"},"node":{"type":"string"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/BlindedMessage"}}}},"InfoReply":{"oneOf":[{"type":"object","required":["id","bill","endorser","submitted","suggested_expiration","status"],"properties":{"bill":{"type":"string"},"endorser":{"type":"string"},"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending"]},"submitted":{"type":"string","format":"date-time"},"suggested_expiration":{"type":"string","format":"date-time"}}},{"type":"object","required":["id","bill","endorser","ttl","signatures","status"],"properties":{"bill":{"type":"string"},"endorser":{"type":"string"},"id":{"type":"string","format":"uuid"},"signatures":{"type":"array","items":{"$ref":"#/components/schemas/BlindSignature"}},"status":{"type":"string","enum":["offered"]},"ttl":{"type":"string","format":"date-time"}}},{"type":"object","required":["id","bill","endorser","status"],"properties":{"bill":{"type":"string"},"endorser":{"type":"string"},"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["denied"]}}},{"type":"object","required":["id","bill","endorser","signatures","status"],"properties":{"bill":{"type":"string"},"endorser":{"type":"string"},"id":{"type":"string","format":"uuid"},"signatures":{"type":"array","items":{"$ref":"#/components/schemas/BlindSignature"}},"status":{"type":"string","enum":["accepted"]}}},{"type":"object","required":["id","bill","endorser","tstamp","status"],"properties":{"bill":{"type":"string"},"endorser":{"type":"string"},"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["rejected"]},"tstamp":{"$ref":"#/components/schemas/TStamp"}}}],"description":"--------------------------- Quote info request","discriminator":{"propertyName":"status"}},"ListReply":{"type":"object","description":"--------------------------- List quotes","required":["quotes"],"properties":{"quotes":{"type":"array","items":{"type":"string","format":"uuid"}}}},"ResolveOffer":{"oneOf":[{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["reject"]}}},{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["accept"]}}}],"description":"--------------------------- Resolve quote"},"ResolveRequest":{"oneOf":[{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["deny"]}}},{"type":"object","required":["discount","action"],"properties":{"action":{"type":"string","enum":["offer"]},"discount":{"type":"string"},"ttl":{"type":"string","format":"date-time","nullable":true}}}],"description":"--------------------------- Resolve quote request","discriminator":{"propertyName":"action"}},"StatusReply":{"oneOf":[{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["pending"]}}},{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["denied"]}}},{"type":"object","required":["signatures","expiration_date","status"],"properties":{"expiration_date":{"$ref":"#/components/schemas/TStamp"},"signatures":{"type":"array","items":{"$ref":"#/components/schemas/BlindSignature"}},"status":{"type":"string","enum":["offered"]}}},{"type":"object","required":["signatures","status"],"properties":{"signatures":{"type":"array","items":{"$ref":"#/components/schemas/BlindSignature"}},"status":{"type":"string","enum":["accepted"]}}},{"type":"object","required":["tstamp","status"],"properties":{"status":{"type":"string","enum":["rejected"]},"tstamp":{"$ref":"#/components/schemas/TStamp"}}}],"description":"--------------------------- Look up quote","discriminator":{"propertyName":"status"}}}}} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fc33ebc..c9d2d87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,11 @@ "name": "wildcat-dashboard-ui", "version": "0.0.1", "dependencies": { + "@hey-api/client-fetch": "^0.8.1", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-hover-card": "^1.1.6", + "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-scroll-area": "^1.2.3", @@ -21,8 +23,8 @@ "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-toggle": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", - "@tailwindcss/vite": "^4.0.7", - "@tanstack/react-query": "^5.66.8", + "@tailwindcss/vite": "^4.0.8", + "@tanstack/react-query": "^5.66.9", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "eslint-plugin-react": "^7.37.4", @@ -31,32 +33,32 @@ "react-dom": "^19.0.0", "react-router": "^7.2.0", "recharts": "^2.15.1", - "tailwind-merge": "^3.0.1", - "tailwindcss": "^4.0.7", + "tailwind-merge": "^3.0.2", + "tailwindcss": "^4.0.8", "tailwindcss-animate": "^1.0.7", "vaul": "^1.1.2" }, "devDependencies": { - "@eslint/js": "^9.19.0", + "@eslint/js": "^9.21.0", + "@hey-api/openapi-ts": "^0.64.5", "@mswjs/data": "^0.16.2", "@testing-library/jest-dom": "^6.6.3", - "@types/node": "^22.13.4", - "@types/react": "^19.0.8", - "@types/react-dom": "^19.0.3", + "@types/node": "^22.13.5", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", "@vitest/coverage-v8": "^3.0.6", - "eslint": "^9.20.1", + "eslint": "^9.21.0", "eslint-config-prettier": "^10.0.1", - "eslint-plugin-prettier": "^5.2.3", - "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-react-refresh": "^0.4.18", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", "globals": "^15.14.0", "jsdom": "^26.0.0", - "msw": "^2.7.0", - "prettier": "^3.5.1", - "typescript": "~5.7.2", - "typescript-eslint": "^8.22.0", - "vite": "^6.1.0", + "msw": "^2.7.1", + "prettier": "^3.5.2", + "typescript": "~5.7.3", + "typescript-eslint": "^8.24.1", + "vite": "^6.1.1", "vitest": "^3.0.6" } }, @@ -598,7 +600,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.11.0", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -608,7 +612,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -630,6 +636,8 @@ }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "license": "MIT", "engines": { "node": ">=18" @@ -639,7 +647,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.20.0", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", + "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -653,10 +663,12 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.6", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.11.0", + "@eslint/core": "^0.12.0", "levn": "^0.4.1" }, "engines": { @@ -701,6 +713,58 @@ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", "license": "MIT" }, + "node_modules/@hey-api/client-fetch": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.8.1.tgz", + "integrity": "sha512-AaVNVLBl7N9Fun7QDnb00YDubAFjB9ICi5U6kmzcJSnnQTQGRMsuBBRupQV5ERdMMFt71zjSDym7Z+gLVe8m3A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/json-schema-ref-parser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.0.2.tgz", + "integrity": "sha512-F6LSkttZcT/XiX3ydeDqTY3uRN3BLJMwyMTk4kg/ichZlKUp3+3Odv0WokSmXGSoZGTW/N66FROMYAm5NPdJlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/openapi-ts": { + "version": "0.64.5", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.64.5.tgz", + "integrity": "sha512-Vl6QQbkb+TXPG/ihobCPj/13KFJPfnjTVljno+T18L2ywL8cRHDFYqi5356ktBixBuwK2cSJvKzxYR6TIMq+AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hey-api/json-schema-ref-parser": "1.0.2", + "c12": "2.0.1", + "commander": "13.0.0", + "handlebars": "4.7.8" + }, + "bin": { + "openapi-ts": "bin/index.cjs" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=22.11.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "typescript": "^5.5.3" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "license": "Apache-2.0", @@ -742,7 +806,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -961,6 +1027,13 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true, + "license": "MIT" + }, "node_modules/@mswjs/data": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@mswjs/data/-/data-0.16.2.tgz", @@ -1072,19 +1145,6 @@ "node": ">=14" } }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, "node_modules/@radix-ui/number": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", @@ -1373,6 +1433,29 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.2.tgz", + "integrity": "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.6.tgz", @@ -1959,42 +2042,42 @@ ] }, "node_modules/@tailwindcss/node": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.7.tgz", - "integrity": "sha512-dkFXufkbRB2mu3FPsW5xLAUWJyexpJA+/VtQj18k3SUiJVLdpgzBd1v1gRRcIpEJj7K5KpxBKfOXlZxT3ZZRuA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.8.tgz", + "integrity": "sha512-FKArQpbrbwv08TNT0k7ejYXpF+R8knZFAatNc0acOxbgeqLzwb86r+P3LGOjIeI3Idqe9CVkZrh4GlsJLJKkkw==", "license": "MIT", "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", - "tailwindcss": "4.0.7" + "tailwindcss": "4.0.8" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.7.tgz", - "integrity": "sha512-yr6w5YMgjy+B+zkJiJtIYGXW+HNYOPfRPtSs+aqLnKwdEzNrGv4ZuJh9hYJ3mcA+HMq/K1rtFV+KsEr65S558g==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.8.tgz", + "integrity": "sha512-KfMcuAu/Iw+DcV1e8twrFyr2yN8/ZDC/odIGta4wuuJOGkrkHZbvJvRNIbQNhGh7erZTYV6Ie0IeD6WC9Y8Hcw==", "license": "MIT", "engines": { "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.0.7", - "@tailwindcss/oxide-darwin-arm64": "4.0.7", - "@tailwindcss/oxide-darwin-x64": "4.0.7", - "@tailwindcss/oxide-freebsd-x64": "4.0.7", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.7", - "@tailwindcss/oxide-linux-arm64-gnu": "4.0.7", - "@tailwindcss/oxide-linux-arm64-musl": "4.0.7", - "@tailwindcss/oxide-linux-x64-gnu": "4.0.7", - "@tailwindcss/oxide-linux-x64-musl": "4.0.7", - "@tailwindcss/oxide-win32-arm64-msvc": "4.0.7", - "@tailwindcss/oxide-win32-x64-msvc": "4.0.7" + "@tailwindcss/oxide-android-arm64": "4.0.8", + "@tailwindcss/oxide-darwin-arm64": "4.0.8", + "@tailwindcss/oxide-darwin-x64": "4.0.8", + "@tailwindcss/oxide-freebsd-x64": "4.0.8", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.8", + "@tailwindcss/oxide-linux-arm64-gnu": "4.0.8", + "@tailwindcss/oxide-linux-arm64-musl": "4.0.8", + "@tailwindcss/oxide-linux-x64-gnu": "4.0.8", + "@tailwindcss/oxide-linux-x64-musl": "4.0.8", + "@tailwindcss/oxide-win32-arm64-msvc": "4.0.8", + "@tailwindcss/oxide-win32-x64-msvc": "4.0.8" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.7.tgz", - "integrity": "sha512-5iQXXcAeOHBZy8ASfHFm1k0O/9wR2E3tKh6+P+ilZZbQiMgu+qrnfpBWYPc3FPuQdWiWb73069WT5D+CAfx/tg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.8.tgz", + "integrity": "sha512-We7K79+Sm4mwJHk26Yzu/GAj7C7myemm7PeXvpgMxyxO70SSFSL3uCcqFbz9JA5M5UPkrl7N9fkBe/Y0iazqpA==", "cpu": [ "arm64" ], @@ -2008,9 +2091,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.7.tgz", - "integrity": "sha512-7yGZtEc5IgVYylqK/2B0yVqoofk4UAbkn1ygNpIJZyrOhbymsfr8uUFCueTu2fUxmAYIfMZ8waWo2dLg/NgLgg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.8.tgz", + "integrity": "sha512-Lv9Isi2EwkCTG1sRHNDi0uRNN1UGFdEThUAGFrydRmQZnraGLMjN8gahzg2FFnOizDl7LB2TykLUuiw833DSNg==", "cpu": [ "arm64" ], @@ -2024,9 +2107,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.7.tgz", - "integrity": "sha512-tPQDV20fBjb26yWbPqT1ZSoDChomMCiXTKn4jupMSoMCFyU7+OJvIY1ryjqBuY622dEBJ8LnCDDWsnj1lX9nNQ==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.8.tgz", + "integrity": "sha512-fWfywfYIlSWtKoqWTjukTHLWV3ARaBRjXCC2Eo0l6KVpaqGY4c2y8snUjp1xpxUtpqwMvCvFWFaleMoz1Vhzlw==", "cpu": [ "x64" ], @@ -2040,9 +2123,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.7.tgz", - "integrity": "sha512-sZqJpTyTZiknU9LLHuByg5GKTW+u3FqM7q7myequAXxKOpAFiOfXpY710FuMY+gjzSapyRbDXJlsTQtCyiTo5w==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.8.tgz", + "integrity": "sha512-SO+dyvjJV9G94bnmq2288Ke0BIdvrbSbvtPLaQdqjqHR83v5L2fWADyFO+1oecHo9Owsk8MxcXh1agGVPIKIqw==", "cpu": [ "x64" ], @@ -2056,9 +2139,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.7.tgz", - "integrity": "sha512-PBgvULgeSswjd8cbZ91gdIcIDMdc3TUHV5XemEpxlqt9M8KoydJzkuB/Dt910jYdofOIaTWRL6adG9nJICvU4A==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.8.tgz", + "integrity": "sha512-ZSHggWiEblQNV69V0qUK5vuAtHP+I+S2eGrKGJ5lPgwgJeAd6GjLsVBN+Mqn2SPVfYM3BOpS9jX/zVg9RWQVDQ==", "cpu": [ "arm" ], @@ -2072,9 +2155,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.7.tgz", - "integrity": "sha512-By/a2yeh+e9b+C67F88ndSwVJl2A3tcUDb29FbedDi+DZ4Mr07Oqw9Y1DrDrtHIDhIZ3bmmiL1dkH2YxrtV+zw==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.8.tgz", + "integrity": "sha512-xWpr6M0OZLDNsr7+bQz+3X7zcnDJZJ1N9gtBWCtfhkEtDjjxYEp+Lr5L5nc/yXlL4MyCHnn0uonGVXy3fhxaVA==", "cpu": [ "arm64" ], @@ -2088,9 +2171,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.7.tgz", - "integrity": "sha512-WHYs3cpPEJb/ccyT20NOzopYQkl7JKncNBUbb77YFlwlXMVJLLV3nrXQKhr7DmZxz2ZXqjyUwsj2rdzd9stYdw==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.8.tgz", + "integrity": "sha512-5tz2IL7LN58ssGEq7h/staD7pu/izF/KeMWdlJ86WDe2Ah46LF3ET6ZGKTr5eZMrnEA0M9cVFuSPprKRHNgjeg==", "cpu": [ "arm64" ], @@ -2104,9 +2187,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.7.tgz", - "integrity": "sha512-7bP1UyuX9kFxbOwkeIJhBZNevKYPXB6xZI37v09fqi6rqRJR8elybwjMUHm54GVP+UTtJ14ueB1K54Dy1tIO6w==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.8.tgz", + "integrity": "sha512-KSzMkhyrxAQyY2o194NKVKU9j/c+NFSoMvnHWFaNHKi3P1lb+Vq1UC19tLHrmxSkKapcMMu69D7+G1+FVGNDXQ==", "cpu": [ "x64" ], @@ -2120,9 +2203,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.7.tgz", - "integrity": "sha512-gBQIV8nL/LuhARNGeroqzXymMzzW5wQzqlteVqOVoqwEfpHOP3GMird5pGFbnpY+NP0fOlsZGrxxOPQ4W/84bQ==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.8.tgz", + "integrity": "sha512-yFYKG5UtHTRimjtqxUWXBgI4Tc6NJe3USjRIVdlTczpLRxq/SFwgzGl5JbatCxgSRDPBFwRrNPxq+ukfQFGdrw==", "cpu": [ "x64" ], @@ -2136,9 +2219,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.7.tgz", - "integrity": "sha512-aH530NFfx0kpQpvYMfWoeG03zGnRCMVlQG8do/5XeahYydz+6SIBxA1tl/cyITSJyWZHyVt6GVNkXeAD30v0Xg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.8.tgz", + "integrity": "sha512-tndGujmCSba85cRCnQzXgpA2jx5gXimyspsUYae5jlPyLRG0RjXbDshFKOheVXU4TLflo7FSG8EHCBJ0EHTKdQ==", "cpu": [ "arm64" ], @@ -2152,9 +2235,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.7.tgz", - "integrity": "sha512-8Cva6bbJN7ZJx320k7vxGGdU0ewmpfS5A4PudyzUuofdi8MgeINuiiWiPQ0VZCda/GX88K6qp+6UpDZNVr8HMQ==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.8.tgz", + "integrity": "sha512-T77jroAc0p4EHVVgTUiNeFn6Nj3jtD3IeNId2X+0k+N1XxfNipy81BEkYErpKLiOkNhpNFjPee8/ZVas29b2OQ==", "cpu": [ "x64" ], @@ -2168,15 +2251,15 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.7.tgz", - "integrity": "sha512-GYx5sxArfIMtdZCsxfya3S/efMmf4RvfqdiLUozkhmSFBNUFnYVodatpoO/en4/BsOIGvq/RB6HwcTLn9prFnQ==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.8.tgz", + "integrity": "sha512-+SAq44yLzYlzyrb7QTcFCdU8Xa7FOA0jp+Xby7fPMUie+MY9HhJysM7Vp+vL8qIp8ceQJfLD+FjgJuJ4lL6nyg==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.0.7", - "@tailwindcss/oxide": "4.0.7", + "@tailwindcss/node": "4.0.8", + "@tailwindcss/oxide": "4.0.8", "lightningcss": "^1.29.1", - "tailwindcss": "4.0.7" + "tailwindcss": "4.0.8" }, "peerDependencies": { "vite": "^5.2.0 || ^6" @@ -2193,9 +2276,9 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.66.8", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.66.8.tgz", - "integrity": "sha512-LqYHYArmM7ycyT1I/Txc/n6KzI8S/hBFw2SQ9Uj1GpbZ89AvZLEvetquiQEHkZ5rFEm+iVNpZ6zYjTiPmJ9N5Q==", + "version": "5.66.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.66.9.tgz", + "integrity": "sha512-NRI02PHJsP5y2gAuWKP+awamTIBFBSKMnO6UVzi03GTclmHHHInH5UzVgzi5tpu4+FmGfsdT7Umqegobtsp23A==", "license": "MIT", "dependencies": { "@tanstack/query-core": "5.66.4" @@ -2355,6 +2438,8 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, "node_modules/@types/lodash": { @@ -2372,9 +2457,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.13.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", - "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", + "version": "22.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", + "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", "devOptional": true, "license": "MIT", "dependencies": { @@ -2788,6 +2873,8 @@ }, "node_modules/acorn": { "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2798,6 +2885,8 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -2815,6 +2904,8 @@ }, "node_modules/ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -2884,6 +2975,8 @@ }, "node_modules/argparse": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, "node_modules/aria-hidden": { @@ -3132,6 +3225,42 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/c12": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/c12/-/c12-2.0.1.tgz", + "integrity": "sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.1", + "confbox": "^0.1.7", + "defu": "^6.1.4", + "dotenv": "^16.4.5", + "giget": "^1.2.3", + "jiti": "^2.3.0", + "mlly": "^1.7.1", + "ohash": "^1.1.4", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -3191,6 +3320,8 @@ }, "node_modules/callsites": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "license": "MIT", "engines": { "node": ">=6" @@ -3266,6 +3397,42 @@ "node": ">= 16" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -3402,10 +3569,37 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.0.0.tgz", + "integrity": "sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "license": "MIT" }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz", + "integrity": "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "dev": true, @@ -3746,6 +3940,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3756,6 +3957,13 @@ "node": ">=0.4.0" } }, + "node_modules/destr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", + "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -3803,6 +4011,19 @@ "csstype": "^3.0.2" } }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -4092,21 +4313,21 @@ } }, "node_modules/eslint": { - "version": "9.20.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", - "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", + "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.11.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.20.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.19.2", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.0", + "@eslint/js": "9.21.0", + "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -4163,37 +4384,6 @@ "eslint": ">=7.0.0" } }, - "node_modules/eslint-plugin-prettier": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", - "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.9.1" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": "*", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, "node_modules/eslint-plugin-react": { "version": "7.37.4", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz", @@ -4271,6 +4461,8 @@ }, "node_modules/espree": { "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.14.0", @@ -4346,15 +4538,10 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/fast-equals": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", @@ -4392,6 +4579,8 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -4504,6 +4693,39 @@ "node": ">= 6" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4623,6 +4845,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/giget": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.5.tgz", + "integrity": "sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.5.4", + "pathe": "^2.0.3", + "tar": "^6.2.1" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -4740,6 +4981,28 @@ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -4902,6 +5165,8 @@ }, "node_modules/import-fresh": { "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -5446,6 +5711,8 @@ }, "node_modules/js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -5512,6 +5779,8 @@ }, "node_modules/json-schema-traverse": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -5984,6 +6253,16 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -5994,14 +6273,74 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mlly": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, "node_modules/ms": { "version": "2.1.3", "license": "MIT" }, "node_modules/msw": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.7.0.tgz", - "integrity": "sha512-BIodwZ19RWfCbYTxWTUfTXc+sg4OwjCAgxU1ZsgmggX/7S3LdUifsbUPJs61j0rWb19CZRGY5if77duhc0uXzw==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.7.1.tgz", + "integrity": "sha512-TVT65uoWt9LE4lMTLBdClHBQVwvZv5ofac1YyE119nCrNyXf4ktdeVnWH9Fyt94Ifmiedhw6Npp4DSuVRSuRpw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6073,6 +6412,20 @@ "version": "1.4.0", "license": "MIT" }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch-native": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz", + "integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==", + "dev": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.19", "dev": true, @@ -6085,6 +6438,27 @@ "dev": true, "license": "MIT" }, + "node_modules/nypm": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.5.4.tgz", + "integrity": "sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "tinyexec": "^0.3.2", + "ufo": "^1.5.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -6185,6 +6559,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz", + "integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==", + "dev": true, + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "license": "MIT", @@ -6259,6 +6640,8 @@ }, "node_modules/parent-module": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -6348,6 +6731,13 @@ "node": ">= 14.16" } }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" @@ -6363,6 +6753,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -6416,9 +6818,9 @@ } }, "node_modules/prettier": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", - "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", + "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", "dev": true, "license": "MIT", "bin": { @@ -6431,19 +6833,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -6501,6 +6890,17 @@ ], "license": "MIT" }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/react": { "version": "19.0.0", "license": "MIT", @@ -6656,6 +7056,20 @@ "react-dom": ">=16.6.0" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/recharts": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz", @@ -6792,6 +7206,8 @@ }, "node_modules/resolve-from": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "license": "MIT", "engines": { "node": ">=4" @@ -7115,6 +7531,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "license": "BSD-3-Clause", @@ -7365,6 +7791,8 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "license": "MIT", "engines": { "node": ">=8" @@ -7402,27 +7830,10 @@ "dev": true, "license": "MIT" }, - "node_modules/synckit": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, "node_modules/tailwind-merge": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.1.tgz", - "integrity": "sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz", + "integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==", "license": "MIT", "funding": { "type": "github", @@ -7430,9 +7841,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.7.tgz", - "integrity": "sha512-yH5bPPyapavo7L+547h3c4jcBXcrKwybQRjwdEIVAd9iXRvy/3T1CC6XSQEgZtRySjKfqvo3Cc0ZF1DTheuIdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.8.tgz", + "integrity": "sha512-Me7N5CKR+D2A1xdWA5t5+kjjT7bwnxZOE6/yDI/ixJdJokszsn2n++mdU5yJwrsTpqFX2B9ZNMBJDwcqk9C9lw==", "license": "MIT" }, "node_modules/tailwindcss-animate": { @@ -7453,6 +7864,41 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/test-exclude": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", @@ -7754,6 +8200,27 @@ "typescript": ">=4.8.4 <5.8.0" } }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -7820,6 +8287,8 @@ }, "node_modules/uri-js": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -8267,6 +8736,13 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", diff --git a/package.json b/package.json index 7e4cca3..772be04 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,15 @@ "format": "prettier --write --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx,json,css,md}'", "docker:build": "docker build --tag wildcat-dashboard-ui .", "docker:rebuild": "npm run docker:build -- --no-cache", - "docker:up": "docker compose up" + "docker:up": "docker compose up", + "openapi-ts": "openapi-ts" }, "dependencies": { + "@hey-api/client-fetch": "^0.8.1", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-hover-card": "^1.1.6", + "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-scroll-area": "^1.2.3", @@ -28,8 +31,8 @@ "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-toggle": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", - "@tailwindcss/vite": "^4.0.7", - "@tanstack/react-query": "^5.66.8", + "@tailwindcss/vite": "^4.0.8", + "@tanstack/react-query": "^5.66.9", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "eslint-plugin-react": "^7.37.4", @@ -38,31 +41,32 @@ "react-dom": "^19.0.0", "react-router": "^7.2.0", "recharts": "^2.15.1", - "tailwind-merge": "^3.0.1", - "tailwindcss": "^4.0.7", + "tailwind-merge": "^3.0.2", + "tailwindcss": "^4.0.8", "tailwindcss-animate": "^1.0.7", "vaul": "^1.1.2" }, "devDependencies": { - "@eslint/js": "^9.19.0", + "@eslint/js": "^9.21.0", + "@hey-api/openapi-ts": "^0.64.5", "@mswjs/data": "^0.16.2", "@testing-library/jest-dom": "^6.6.3", - "@types/node": "^22.13.4", - "@types/react": "^19.0.8", - "@types/react-dom": "^19.0.3", + "@types/node": "^22.13.5", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", "@vitest/coverage-v8": "^3.0.6", - "eslint": "^9.20.1", + "eslint": "^9.21.0", "eslint-config-prettier": "^10.0.1", - "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-react-refresh": "^0.4.18", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", "globals": "^15.14.0", "jsdom": "^26.0.0", - "msw": "^2.7.0", - "prettier": "^3.5.1", - "typescript": "~5.7.2", - "typescript-eslint": "^8.22.0", - "vite": "^6.1.0", + "msw": "^2.7.1", + "prettier": "^3.5.2", + "typescript": "~5.7.3", + "typescript-eslint": "^8.24.1", + "vite": "^6.1.1", "vitest": "^3.0.6" }, "msw": { diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index ec47a9a..7f2f4b7 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -8,7 +8,7 @@ * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.7.0' +const PACKAGE_VERSION = '2.7.1' const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() diff --git a/src/components/AppSidebar.tsx b/src/components/AppSidebar.tsx index babd8db..f4c6fbd 100644 --- a/src/components/AppSidebar.tsx +++ b/src/components/AppSidebar.tsx @@ -20,7 +20,7 @@ const items = [ icon: Home, }, { - title: "Balance", + title: "Balances", url: "/balances", icon: Bitcoin, }, diff --git a/src/components/BalanceChart.tsx b/src/components/BalanceChart.tsx deleted file mode 100644 index 66e140c..0000000 --- a/src/components/BalanceChart.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts" -import { type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent } from "@/components/ui/chart" - -const config = { - bitcoin: { - label: "Bitcoin", - color: "#2563eb", - }, -} satisfies ChartConfig - -export function BalanceChart() { - const data = [ - { month: "January", bitcoin: 186 }, - { month: "February", bitcoin: 305 }, - { month: "March", bitcoin: 237 }, - { month: "April", bitcoin: 73 }, - { month: "May", bitcoin: 209 }, - { month: "June", bitcoin: 214 }, - { month: "July", bitcoin: 21 }, - { month: "August", bitcoin: 32 }, - { month: "September", bitcoin: 0 }, - { month: "October", bitcoin: 0 }, - { month: "November", bitcoin: 0 }, - { month: "December", bitcoin: 0 }, - ] - - return ( - - - - value.slice(0, 3)} - /> - - - } /> - - - ) -} diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index 190c99a..97308d9 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -9,7 +9,7 @@ import { } from "@/components/ui/breadcrumb" import { Link } from "react-router" -export function Breadcrumbs({ children }: PropsWithChildren) { +export function Breadcrumbs({ parents, children }: PropsWithChildren<{ parents?: React.ReactNode[] }>) { return ( @@ -19,6 +19,20 @@ export function Breadcrumbs({ children }: PropsWithChildren) { + {parents && ( + <> + {parents.map((it, index) => ( + <> + + + {it} + + + + + ))} + + )} {children} diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 0000000..a43fd7f --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,22 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" + +import { cn } from "@/lib/utils" + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Label } diff --git a/src/generated/client/@tanstack/react-query.gen.ts b/src/generated/client/@tanstack/react-query.gen.ts new file mode 100644 index 0000000..1435e8f --- /dev/null +++ b/src/generated/client/@tanstack/react-query.gen.ts @@ -0,0 +1,198 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type Options, adminLookupQuote, resolveQuote, listAcceptedQuotes, listPendingQuotes, enquireQuote, lookupQuote, resolveOffer } from '../sdk.gen'; +import { queryOptions, type UseMutationOptions, type DefaultError } from '@tanstack/react-query'; +import type { AdminLookupQuoteData, ResolveQuoteData, ListAcceptedQuotesData, ListPendingQuotesData, EnquireQuoteData, EnquireQuoteResponse, LookupQuoteData, ResolveOfferData } from '../types.gen'; +import { client as _heyApiClient } from '../client.gen'; + +export type QueryKey = [ + Pick & { + _id: string; + _infinite?: boolean; + } +]; + +const createQueryKey = (id: string, options?: TOptions, infinite?: boolean): [ + QueryKey[0] +] => { + const params: QueryKey[0] = { _id: id, baseUrl: (options?.client ?? _heyApiClient).getConfig().baseUrl } as QueryKey[0]; + if (infinite) { + params._infinite = infinite; + } + if (options?.body) { + params.body = options.body; + } + if (options?.headers) { + params.headers = options.headers; + } + if (options?.path) { + params.path = options.path; + } + if (options?.query) { + params.query = options.query; + } + return [ + params + ]; +}; + +export const adminLookupQuoteQueryKey = (options: Options) => createQueryKey('adminLookupQuote', options); + +export const adminLookupQuoteOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await adminLookupQuote({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: adminLookupQuoteQueryKey(options) + }); +}; + +export const resolveQuoteQueryKey = (options: Options) => createQueryKey('resolveQuote', options); + +export const resolveQuoteOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await resolveQuote({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: resolveQuoteQueryKey(options) + }); +}; + +export const resolveQuoteMutation = (options?: Partial>) => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (localOptions) => { + const { data } = await resolveQuote({ + ...options, + ...localOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +export const listAcceptedQuotesQueryKey = (options?: Options) => createQueryKey('listAcceptedQuotes', options); + +export const listAcceptedQuotesOptions = (options?: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await listAcceptedQuotes({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: listAcceptedQuotesQueryKey(options) + }); +}; + +export const listPendingQuotesQueryKey = (options?: Options) => createQueryKey('listPendingQuotes', options); + +export const listPendingQuotesOptions = (options?: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await listPendingQuotes({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: listPendingQuotesQueryKey(options) + }); +}; + +export const enquireQuoteQueryKey = (options: Options) => createQueryKey('enquireQuote', options); + +export const enquireQuoteOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await enquireQuote({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: enquireQuoteQueryKey(options) + }); +}; + +export const enquireQuoteMutation = (options?: Partial>) => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (localOptions) => { + const { data } = await enquireQuote({ + ...options, + ...localOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +export const lookupQuoteQueryKey = (options: Options) => createQueryKey('lookupQuote', options); + +export const lookupQuoteOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await lookupQuote({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: lookupQuoteQueryKey(options) + }); +}; + +export const resolveOfferQueryKey = (options: Options) => createQueryKey('resolveOffer', options); + +export const resolveOfferOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await resolveOffer({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: resolveOfferQueryKey(options) + }); +}; + +export const resolveOfferMutation = (options?: Partial>) => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (localOptions) => { + const { data } = await resolveOffer({ + ...options, + ...localOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; \ No newline at end of file diff --git a/src/generated/client/client.gen.ts b/src/generated/client/client.gen.ts new file mode 100644 index 0000000..6759c1f --- /dev/null +++ b/src/generated/client/client.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from '@hey-api/client-fetch'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig()); \ No newline at end of file diff --git a/src/generated/client/index.ts b/src/generated/client/index.ts new file mode 100644 index 0000000..e64537d --- /dev/null +++ b/src/generated/client/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/generated/client/sdk.gen.ts b/src/generated/client/sdk.gen.ts new file mode 100644 index 0000000..9afe174 --- /dev/null +++ b/src/generated/client/sdk.gen.ts @@ -0,0 +1,86 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +import type { AdminLookupQuoteData, AdminLookupQuoteResponse, ResolveQuoteData, ListAcceptedQuotesData, ListAcceptedQuotesResponse, ListPendingQuotesData, ListPendingQuotesResponse, EnquireQuoteData, EnquireQuoteResponse, LookupQuoteData, LookupQuoteResponse, ResolveOfferData } from './types.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +export const adminLookupQuote = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + url: '/v1/admin/credit/quote/{id}', + ...options + }); +}; + +export const resolveQuote = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/v1/admin/credit/quote/{id}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +export const listAcceptedQuotes = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + url: '/v1/admin/credit/quote/accepted', + ...options + }); +}; + +/** + * --------------------------- List quotes + */ +export const listPendingQuotes = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + url: '/v1/admin/credit/quote/pending', + ...options + }); +}; + +/** + * --------------------------- Enquire mint quote + */ +export const enquireQuote = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/v1/credit/mint/quote', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; + +export const lookupQuote = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + url: '/v1/credit/mint/quote/{id}', + ...options + }); +}; + +export const resolveOffer = (options: Options) => { + return (options.client ?? _heyApiClient).post({ + url: '/v1/credit/quote/{id}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } + }); +}; \ No newline at end of file diff --git a/src/generated/client/types.gen.ts b/src/generated/client/types.gen.ts new file mode 100644 index 0000000..19c19ab --- /dev/null +++ b/src/generated/client/types.gen.ts @@ -0,0 +1,330 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * Manually added - should be replaced with generated one + */ +export type Id = string; + +/** + * Manually added - should be replaced with generated one + */ +export type BlindSignatureDleq = string; + +/** + * Manually added - should be replaced with generated one + */ +export type Witness = string; + +/** + * Manually added - should be replaced with generated one + */ +export type TStamp = number; + +/** + * Manually added - should be replaced with generated one + */ +export type Resolve = number; + +/** + * Amount can be any unit + */ +export type Amount = number; + +/** + * Blind Signature (also called `promise`) + */ +export type BlindSignature = { + /** + * Blinded signature (C_) + * + * The blinded signature on the secret message `B_` of [BlindedMessage]. + */ + C_: string; + amount: Amount; + dleq?: BlindSignatureDleq | null; + id: Id; +}; + +/** + * Blinded Message (also called `output`) + */ +export type BlindedMessage = { + /** + * Blinded secret message (B_) + * + * The blinded secret message generated by the sender. + */ + B_: string; + amount: Amount; + id: Id; + witness?: Witness | null; +}; + +export type EnquireReply = { + id: string; +}; + +/** + * --------------------------- Enquire mint quote + */ +export type EnquireRequest = { + bill: string; + node: string; + outputs: Array; +}; + +/** + * --------------------------- Quote info request + */ +export type InfoReply = { + bill: string; + endorser: string; + id: string; + status: 'pending'; + submitted: string; + suggested_expiration: string; +} | { + bill: string; + endorser: string; + id: string; + signatures: Array; + status: 'offered'; + ttl: string; +} | { + bill: string; + endorser: string; + id: string; + status: 'denied'; +} | { + bill: string; + endorser: string; + id: string; + signatures: Array; + status: 'accepted'; +} | { + bill: string; + endorser: string; + id: string; + status: 'rejected'; + tstamp: TStamp; +}; + +/** + * --------------------------- List quotes + */ +export type ListReply = { + quotes: Array; +}; + +/** + * --------------------------- Resolve quote + */ +export type ResolveOffer = { + action: 'reject'; +} | { + action: 'accept'; +}; + +/** + * --------------------------- Resolve quote request + */ +export type ResolveRequest = { + action: 'deny'; +} | { + action: 'offer'; + discount: string; + ttl?: string | null; +}; + +/** + * --------------------------- Look up quote + */ +export type StatusReply = { + status: 'pending'; +} | { + status: 'denied'; +} | { + expiration_date: TStamp; + signatures: Array; + status: 'offered'; +} | { + signatures: Array; + status: 'accepted'; +} | { + status: 'rejected'; + tstamp: TStamp; +}; + +export type AdminLookupQuoteData = { + body?: never; + path: { + /** + * The quote id + */ + id: string; + }; + query?: never; + url: '/v1/admin/credit/quote/{id}'; +}; + +export type AdminLookupQuoteErrors = { + /** + * Quote id not found + */ + 404: unknown; +}; + +export type AdminLookupQuoteResponses = { + /** + * Succesful response + */ + 200: InfoReply; +}; + +export type AdminLookupQuoteResponse = AdminLookupQuoteResponses[keyof AdminLookupQuoteResponses]; + +export type ResolveQuoteData = { + body: ResolveRequest; + path: { + /** + * The quote id + */ + id: string; + }; + query?: never; + url: '/v1/admin/credit/quote/{id}'; +}; + +export type ResolveQuoteResponses = { + /** + * Succesful response + */ + 200: unknown; +}; + +export type ListAcceptedQuotesData = { + body?: never; + path?: never; + query?: { + /** + * only accepted quotes younger than `since` + */ + since?: string | null; + }; + url: '/v1/admin/credit/quote/accepted'; +}; + +export type ListAcceptedQuotesResponses = { + /** + * Succesful response + */ + 200: ListReply; +}; + +export type ListAcceptedQuotesResponse = ListAcceptedQuotesResponses[keyof ListAcceptedQuotesResponses]; + +export type ListPendingQuotesData = { + body?: never; + path?: never; + query?: { + /** + * only quote requests younger than `since` + */ + since?: string | null; + }; + url: '/v1/admin/credit/quote/pending'; +}; + +export type ListPendingQuotesResponses = { + /** + * Succesful response + */ + 200: ListReply; +}; + +export type ListPendingQuotesResponse = ListPendingQuotesResponses[keyof ListPendingQuotesResponses]; + +export type EnquireQuoteData = { + body: EnquireRequest; + path?: never; + query?: never; + url: '/v1/credit/mint/quote'; +}; + +export type EnquireQuoteErrors = { + /** + * Quote request not accepted + */ + 404: unknown; +}; + +export type EnquireQuoteResponses = { + /** + * Quote request admitted + */ + 200: EnquireReply; +}; + +export type EnquireQuoteResponse = EnquireQuoteResponses[keyof EnquireQuoteResponses]; + +export type LookupQuoteData = { + body?: never; + path: { + /** + * The quote id + */ + id: string; + }; + query?: never; + url: '/v1/credit/mint/quote/{id}'; +}; + +export type LookupQuoteErrors = { + /** + * Quote id not found + */ + 404: unknown; +}; + +export type LookupQuoteResponses = { + /** + * Succesful response + */ + 200: StatusReply; +}; + +export type LookupQuoteResponse = LookupQuoteResponses[keyof LookupQuoteResponses]; + +export type ResolveOfferData = { + body: Resolve; + path: { + /** + * The quote id + */ + id: string; + }; + query?: never; + url: '/v1/credit/quote/{id}'; +}; + +export type ResolveOfferErrors = { + /** + * Quote not found + */ + 404: unknown; + /** + * Quote already resolved + */ + 409: unknown; +}; + +export type ResolveOfferResponses = { + /** + * Succesful response + */ + 200: unknown; +}; + +export type ClientOptions = { + baseUrl: `${string}://opt` | (string & {}); +}; \ No newline at end of file diff --git a/src/hooks/use-api-client.ts b/src/hooks/use-api-client.ts new file mode 100644 index 0000000..ca37afe --- /dev/null +++ b/src/hooks/use-api-client.ts @@ -0,0 +1,5 @@ +import * as client from "@/generated/client" + +export default function useApiClient() { + return client +} diff --git a/src/hooks/use-local-storage.ts b/src/hooks/use-local-storage.ts new file mode 100644 index 0000000..a04c737 --- /dev/null +++ b/src/hooks/use-local-storage.ts @@ -0,0 +1,31 @@ +import { useState } from "react" +import { getItem, setItem, removeItem } from "@/utils/local-storage" + +type DispatchAction = T | ((prevState: T) => T) + +export default function useLocalStorage(key: string, initialValue: T) { + const [value, setValue] = useState(() => { + const data = getItem(key) + return (data || initialValue) as T + }) + + function handleDispatch(action: DispatchAction) { + if (typeof action === "function") { + setValue((prevState) => { + const newValue = (action as (prevState: T) => T)(prevState) + setItem(key, newValue) + return newValue + }) + } else { + setValue(action) + setItem(key, action) + } + } + + function clearState() { + setValue(undefined as T) + removeItem(key) + } + + return [value, handleDispatch, clearState] as const +} diff --git a/src/layout.tsx b/src/layout.tsx index f9763a0..ef91f02 100644 --- a/src/layout.tsx +++ b/src/layout.tsx @@ -8,7 +8,7 @@ export default function Layout() {
- +
diff --git a/src/lib/api.ts b/src/lib/api.ts index 6acdcde..e732b8e 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,5 +1,30 @@ -import { ADMIN_QUOTE_ACCEPTED, ADMIN_QUOTE_BY_ID, ADMIN_QUOTE_PENDING, INFO } from "@/constants/endpoints" -import { apiFetch } from "@/utils/api" +import { API_URL } from "@/constants/api" +import { BALANCES, INFO } from "@/constants/endpoints" + +const apiFetch = async (endpoint: string, options: RequestInit = {}): Promise => { + const url = `${API_URL}${endpoint}` + + const response = await fetch(url, { + ...options, + /* headers: { + "Content-Type": "application/json", + ...(options.headers || {}), + }, */ + headers: options.headers ?? [], + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.statusText}`) + } + + const contentLength = response.headers.get("Content-Length") + + if (contentLength === "0" || response.headers.get("Content-Type")?.includes("application/json") === false) { + return {} as T + } + + return response.json() as Promise +} export interface InfoResponse { name?: string @@ -33,48 +58,27 @@ export async function fetchInfo(): Promise { }) } -interface QuotePending { - id: string - bill: string - endorser: string - submitted: number - suggested_expiration: number -} -interface QuoteAccepted { - id: string - bill: string - endorser: string - ttl: number - signatures: unknown[] -} -interface QuoteDeclined { - id: string - bill: string - endorser: string -} - -export type QuoteInfoReply = QuotePending | QuoteAccepted | QuoteDeclined - -export interface QuoteListResponse { - quotes: string[] +export interface BalancesResponse { + bitcoin: { + value: string + currency: string + } + eiou: { + value: string + currency: string + } + debit: { + value: string + currency: string + } + credit: { + value: string + currency: string + } } -export async function fetchAdminQuoteById(id: string): Promise { - return apiFetch(ADMIN_QUOTE_BY_ID.replace(":id", id), { - headers: { - "Content-Type": "application/json", - }, - }) -} -export async function fetchAdminQuotePending(): Promise { - return apiFetch(ADMIN_QUOTE_PENDING, { - headers: { - "Content-Type": "application/json", - }, - }) -} -export async function fetchAdminQuoteAccepted(): Promise { - return apiFetch(ADMIN_QUOTE_ACCEPTED, { +export async function fetchBalances(): Promise { + return apiFetch(BALANCES, { headers: { "Content-Type": "application/json", }, diff --git a/src/main.tsx b/src/main.tsx index 5642cfd..9ae3ffc 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -10,6 +10,7 @@ import SettingsPage from "./pages/settings/SettingsPage" import meta from "./constants/meta" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" import InfoPage from "./pages/info/InfoPage" +import QuotePage from "./pages/quotes/QuotePage" const queryClient = new QueryClient() @@ -30,6 +31,7 @@ void prepare().then(() => { } /> } /> } /> + } /> } /> } /> diff --git a/src/mocks/db.ts b/src/mocks/db.ts index 4478972..c4a9583 100644 --- a/src/mocks/db.ts +++ b/src/mocks/db.ts @@ -7,10 +7,66 @@ export const db = factory({ pubkey: nullable(String), version: nullable(String), }, + quotes: { + id: primaryKey(String), + bill: nullable(String), + endorser: nullable(String), + status: nullable(String), + submitted: nullable(String), // pending + suggested_expiration: nullable(String), // pending + ttl: nullable(String), // offered + signatures: Array, // accepted + tstamp: nullable(String), // rejected + }, }) db.info.create({ + id: "0283bf290884eed3a7ca2663fc0260de2e2064d6b355ea13f98dec004b7a7ead99", name: "Bob's Wildcat mint", pubkey: "0283bf290884eed3a7ca2663fc0260de2e2064d6b355ea13f98dec004b7a7ead99", version: "Nutshell/0.15.0", }) + +db.quotes.create({ + id: "63777d15-ce53-4cca-94bf-7726c7930aab", + status: "pending", +}) + +db.quotes.create({ + id: "cd3fc93c-4507-4154-b51b-215b6f360a53", + status: "pending", +}) + +db.quotes.create({ + id: "d20ab61f-03c5-479f-930b-b6aca608b1e6", + status: "pending", +}) + +db.quotes.create({ + id: "57330ad9-30b1-45a7-b900-a37be37005d3", + status: "offered", +}) + +db.quotes.create({ + id: "62ea00be-66f2-4b04-b2c4-257d7409ce9f", + status: "offered", +}) + +db.quotes.create({ + id: "825e4fa8-dab3-4072-bb76-d58a975154af", + status: "accepted", +}) + +db.quotes.create({ + id: "0e96e7cc-4327-41c6-87bf-8096fd880117", + status: "accepted", +}) + +db.quotes.create({ + id: "38b835a4-dc5d-4433-8592-81c49c94d505", + status: "rejected", +}) +db.quotes.create({ + id: "2f5bc589-9bca-4899-adbf-76c881a4e418", + status: "rejected", +}) diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 91386ed..c6770eb 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,4 +1,5 @@ -import { fetchAdminQuotePending } from "./handlers/admin_quotes" +import { fetchAdminLookupQuote, fetchAdminQuotePending } from "./handlers/admin_quotes" +import { fetchBalances } from "./handlers/balances" import { fetchInfo } from "./handlers/info" -export const handlers = [fetchInfo, fetchAdminQuotePending] +export const handlers = [fetchInfo, fetchBalances, fetchAdminQuotePending, fetchAdminLookupQuote] diff --git a/src/mocks/handlers/admin_quotes.ts b/src/mocks/handlers/admin_quotes.ts index e3ad071..d1b237b 100644 --- a/src/mocks/handlers/admin_quotes.ts +++ b/src/mocks/handlers/admin_quotes.ts @@ -1,24 +1,35 @@ -import { http, delay, HttpResponse } from "msw" +import { http, delay, HttpResponse, StrictResponse } from "msw" import { API_URL } from "@/constants/api" -import type { QuoteListResponse } from "@/lib/api" -import { ADMIN_QUOTE_PENDING } from "@/constants/endpoints" +import { ADMIN_QUOTE_BY_ID, ADMIN_QUOTE_PENDING } from "@/constants/endpoints" +import { AdminLookupQuoteResponse, InfoReply, ListPendingQuotesResponse } from "@/generated/client" +import { db } from "../db" -export const fetchAdminQuotePending = http.get( +export const fetchAdminQuotePending = http.get( `${API_URL}${ADMIN_QUOTE_PENDING}`, async () => { await delay(1_000) + const data = db.quotes.getAll().filter((it) => it.status === "pending") + return HttpResponse.json({ - quotes: [ - "63777d15-ce53-4cca-94bf-7726c7930aab", - "cd3fc93c-4507-4154-b51b-215b6f360a53", - "d20ab61f-03c5-479f-930b-b6aca608b1e6", - "57330ad9-30b1-45a7-b900-a37be37005d3", - "62ea00be-66f2-4b04-b2c4-257d7409ce9f", - "597d3d44-4a65-4c87-8d50-628acda245f5", - "0e96e7cc-4327-41c6-87bf-8096fd880117", - "2f5bc589-9bca-4899-adbf-76c881a4e418", - ], + quotes: data.map((it) => it.id), }) }, ) + +export const fetchAdminLookupQuote = http.get( + `${API_URL}${ADMIN_QUOTE_BY_ID}`, + async ({ params }) => { + const { id } = params + + await delay(1_000) + + const data = db.quotes.getAll().filter((it) => it.id === id) + + if (data.length === 0) { + return HttpResponse.json(null, { status: 404 }) as unknown as StrictResponse + } + + return HttpResponse.json(data[0] as InfoReply) + }, +) diff --git a/src/mocks/handlers/balances.ts b/src/mocks/handlers/balances.ts new file mode 100644 index 0000000..3c0bcd2 --- /dev/null +++ b/src/mocks/handlers/balances.ts @@ -0,0 +1,27 @@ +import { http, delay, HttpResponse } from "msw" +import { API_URL } from "@/constants/api" +import type { BalancesResponse } from "@/lib/api" +import { BALANCES } from "@/constants/endpoints" + +export const fetchBalances = http.get(`${API_URL}${BALANCES}`, async () => { + await delay(1_000) + + return HttpResponse.json({ + bitcoin: { + value: "42.12345678", + currency: "BTC", + }, + eiou: { + value: "1.12345678", + currency: "BTC", + }, + debit: { + value: "0.12345678", + currency: "BTC", + }, + credit: { + value: "0.00000042", + currency: "BTC", + }, + }) +}) diff --git a/src/pages/balances/BalancesPage.tsx b/src/pages/balances/BalancesPage.tsx index 42ad8ad..807c72b 100644 --- a/src/pages/balances/BalancesPage.tsx +++ b/src/pages/balances/BalancesPage.tsx @@ -1,6 +1,202 @@ -import { BalanceChart } from "@/components/BalanceChart" import { Breadcrumbs } from "@/components/Breadcrumbs" import { PageTitle } from "@/components/PageTitle" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts" +import { type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent } from "@/components/ui/chart" +import { PropsWithChildren, Suspense } from "react" +import { Skeleton } from "@/components/ui/skeleton" +import { BalancesResponse, fetchBalances } from "@/lib/api" +import { useSuspenseQuery } from "@tanstack/react-query" +import useLocalStorage from "@/hooks/use-local-storage" + +function Loader() { + return ( +
+ + + + +
+ ) +} + +export function BitcoinBalanceChart() { + const config = { + bitcoin: { + label: "Bitcoin", + color: "#2563eb", + }, + } satisfies ChartConfig + + const data = [ + { month: "January", bitcoin: 186 }, + { month: "February", bitcoin: 305 }, + { month: "March", bitcoin: 237 }, + { month: "April", bitcoin: 73 }, + { month: "May", bitcoin: 209 }, + { month: "June", bitcoin: 214 }, + { month: "July", bitcoin: 21 }, + { month: "August", bitcoin: 32 }, + { month: "September", bitcoin: 0 }, + { month: "October", bitcoin: 0 }, + { month: "November", bitcoin: 0 }, + { month: "December", bitcoin: 0 }, + ] + + return ( + + + + value.slice(0, 3)} + /> + + + } /> + + + ) +} + +export function OtherBalanceChart() { + const config = { + eIOU: { + label: "e-IOU", + color: "#911198", + }, + credit: { + label: "Credit token", + color: "#e9d4ff", + }, + debit: { + label: "Debit token", + color: "#c27aff", + }, + } satisfies ChartConfig + + const data = [ + { month: "January", credit: 121, debit: 0 }, + { month: "February", credit: 231, debit: 0 }, + { month: "March", credit: 321, debit: 51 }, + { month: "April", credit: 603, debit: 186 }, + { month: "May", credit: 583, debit: 486 }, + { month: "June", credit: 893, debit: 359 }, + { month: "July", credit: 1023, debit: 192 }, + { month: "August", credit: 2023, debit: 521 }, + { month: "September", credit: 1821, debit: 789 }, + { month: "October", credit: 1782, debit: 1232 }, + { month: "November", credit: 0, debit: 0 }, + { month: "December", credit: 0, debit: 0 }, + ] + + return ( + + + + value.slice(0, 3)} + /> + + + + + } /> + + + ) +} + +export function BalanceText({ value, children }: PropsWithChildren<{ value: BalancesResponse["bitcoin"] }>) { + return ( + <> +

+ {value.value} {value.currency} +

+ {children} + + ) +} + +function PageBody() { + const { data } = useSuspenseQuery({ + queryKey: ["balances"], + queryFn: fetchBalances, + }) + + return ( + <> +
+ + + Bitcoin balance + + + + + + + + e-IOU balance + + + + + + + + Credit token balance + + + + + + + + Debit token balance + + + + + +
+ +
+ + + + + + +
+ + ) +} + +function DevSection() { + const [devMode] = useLocalStorage("devMode", false) + const { data } = useSuspenseQuery({ + queryKey: ["balances"], + queryFn: fetchBalances, + }) + + return ( + <> + {devMode && ( +
+          {JSON.stringify(data, null, 2)}
+        
+ )} + + ) +} export default function BalancesPage() { return ( @@ -8,9 +204,10 @@ export default function BalancesPage() { Balances Balances -
- -
+ }> + + + ) } diff --git a/src/pages/quotes/QuotePage.tsx b/src/pages/quotes/QuotePage.tsx new file mode 100644 index 0000000..a455bfb --- /dev/null +++ b/src/pages/quotes/QuotePage.tsx @@ -0,0 +1,114 @@ +import { Breadcrumbs } from "@/components/Breadcrumbs" +import { PageTitle } from "@/components/PageTitle" +import { Skeleton } from "@/components/ui/skeleton" +import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table" +import { InfoReply } from "@/generated/client" +import { adminLookupQuoteOptions } from "@/generated/client/@tanstack/react-query.gen" +import useLocalStorage from "@/hooks/use-local-storage" +import { useSuspenseQuery } from "@tanstack/react-query" +import { Suspense } from "react" +import { Link, useParams } from "react-router" + +function Loader() { + return ( +
+ +
+ ) +} + +function Quote({ value }: { value: InfoReply }) { + return ( + <> +
+ + + + id: + {value.id} + + + status: + {value.status} + + + endorser: + {value.endorser || "(empty)"} + + + bill: + {value.bill || "(empty)"} + + +
+
+ + ) +} + +function DevSection({ id }: { id: InfoReply["id"] }) { + const [devMode] = useLocalStorage("devMode", false) + + const { data } = useSuspenseQuery({ + ...adminLookupQuoteOptions({ + path: { + id, + }, + }), + }) + + return ( + <> + {devMode && ( + <> +
+            {JSON.stringify(data, null, 2)}
+          
+ + )} + + ) +} + +function PageBody({ id }: { id: InfoReply["id"] }) { + const { data } = useSuspenseQuery({ + ...adminLookupQuoteOptions({ + path: { + id, + }, + }), + }) + + return ( + <> + + + ) +} + +export default function QuotePage() { + const { id } = useParams<{ id: InfoReply["id"] }>() + + if (!id) { + throw Error("Missing `id` param.") + } + + return ( + <> + + Quotes + , + ]} + > + {id} + + Quote {id} + }> + + + + + ) +} diff --git a/src/pages/quotes/QuotesPage.tsx b/src/pages/quotes/QuotesPage.tsx index a389940..8519a54 100644 --- a/src/pages/quotes/QuotesPage.tsx +++ b/src/pages/quotes/QuotesPage.tsx @@ -3,10 +3,12 @@ import { H3 } from "@/components/Headings" import { PageTitle } from "@/components/PageTitle" import { Button } from "@/components/ui/button" import { Skeleton } from "@/components/ui/skeleton" -import { fetchAdminQuotePending } from "@/lib/api" +import { listPendingQuotesOptions } from "@/generated/client/@tanstack/react-query.gen" +import useLocalStorage from "@/hooks/use-local-storage" import { useSuspenseQuery } from "@tanstack/react-query" import { ViewIcon } from "lucide-react" import { Suspense } from "react" +import { Link, useNavigate } from "react-router" function Loader() { return ( @@ -18,8 +20,7 @@ function Loader() { function QuoteListPendingRaw() { const { data } = useSuspenseQuery({ - queryKey: ["quotes-pending"], - queryFn: fetchAdminQuotePending, + ...listPendingQuotesOptions({}), }) return ( @@ -32,9 +33,10 @@ function QuoteListPendingRaw() { } function QuoteListPending() { + const navigate = useNavigate() + const { data } = useSuspenseQuery({ - queryKey: ["quotes-pending"], - queryFn: fetchAdminQuotePending, + ...listPendingQuotesOptions({}), }) return ( @@ -43,8 +45,13 @@ function QuoteListPending() { {data.quotes.map((it, index) => { return (
- {it} -
@@ -55,13 +62,18 @@ function QuoteListPending() { ) } +function DevSection() { + const [devMode] = useLocalStorage("devMode", false) + + return <>{devMode && } +} + function PageBody() { return ( <>

Pending

}> - ) @@ -73,6 +85,9 @@ export default function QuotesPage() { Quotes Quotes + + + ) } diff --git a/src/pages/settings/SettingsPage.tsx b/src/pages/settings/SettingsPage.tsx index 1eb45b3..9178ac0 100644 --- a/src/pages/settings/SettingsPage.tsx +++ b/src/pages/settings/SettingsPage.tsx @@ -1,11 +1,50 @@ import { Breadcrumbs } from "@/components/Breadcrumbs" import { PageTitle } from "@/components/PageTitle" +import { Label } from "@/components/ui/label" +import { Skeleton } from "@/components/ui/skeleton" +import { Switch } from "@/components/ui/switch" +import useLocalStorage from "@/hooks/use-local-storage" +import { Suspense } from "react" + +function Loader() { + return ( +
+ +
+ ) +} + +function PageBody() { + const [devMode, setDevMode] = useLocalStorage("devMode", false) + + return ( + <> +
+
+ { + setDevMode((it) => !it) + }} + /> + +
+
+ + ) +} export default function SettingsPage() { return ( <> Settings Settings + + }> + + ) } diff --git a/src/utils/api.ts b/src/utils/api.ts deleted file mode 100644 index ae08133..0000000 --- a/src/utils/api.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { API_URL } from "@/constants/api" - -export const apiFetch = async (endpoint: string, options: RequestInit = {}): Promise => { - const url = `${API_URL}${endpoint}` - - const response = await fetch(url, { - ...options, - /* headers: { - "Content-Type": "application/json", - ...(options.headers || {}), - }, */ - headers: options.headers ?? [], - }) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.statusText}`) - } - - const contentLength = response.headers.get("Content-Length") - - if (contentLength === "0" || response.headers.get("Content-Type")?.includes("application/json") === false) { - return {} as T - } - - return response.json() as Promise -} diff --git a/src/utils/local-storage.ts b/src/utils/local-storage.ts new file mode 100644 index 0000000..df628c1 --- /dev/null +++ b/src/utils/local-storage.ts @@ -0,0 +1,24 @@ +export function setItem(key: string, value: unknown) { + try { + window.localStorage.setItem(key, JSON.stringify(value)) + } catch (err) { + console.error(err) + } +} + +export function getItem(key: string): T | undefined { + try { + const data = window.localStorage.getItem(key) + return data ? (JSON.parse(data) as T) : undefined + } catch (err) { + console.error(err) + } +} + +export function removeItem(key: string) { + try { + window.localStorage.removeItem(key) + } catch (err) { + console.error(err) + } +} diff --git a/tsconfig.app.json b/tsconfig.app.json index 6cc8494..2db790a 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -29,6 +29,7 @@ }, "include": [ "src", - "vitest-setup.ts" + "vitest-setup.ts", + "openapi-ts.config.ts" ] }