diff --git a/pkgs/clan-app/ui/package-lock.json b/pkgs/clan-app/ui/package-lock.json
index b8543f65f..e3616b477 100644
--- a/pkgs/clan-app/ui/package-lock.json
+++ b/pkgs/clan-app/ui/package-lock.json
@@ -15,9 +15,10 @@
"@modular-forms/solid": "^0.25.1",
"@solid-primitives/storage": "^4.3.2",
"@solidjs/router": "^0.15.3",
- "@tanstack/eslint-plugin-query": "^5.51.12",
- "@tanstack/solid-query": "^5.76.0",
- "@tanstack/solid-query-devtools": "^5.83.0",
+ "@tanstack/eslint-plugin-query": "^5.83.1",
+ "@tanstack/solid-query": "^5.85.5",
+ "@tanstack/solid-query-devtools": "^5.85.5",
+ "@tanstack/solid-query-persist-client": "^5.85.5",
"solid-js": "^1.9.7",
"solid-toast": "^0.5.0",
"three": "^0.176.0",
@@ -2487,12 +2488,12 @@
}
},
"node_modules/@tanstack/eslint-plugin-query": {
- "version": "5.81.2",
- "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.81.2.tgz",
- "integrity": "sha512-h4k6P6fm5VhKP5NkK+0TTVpGGyKQdx6tk7NYYG7J7PkSu7ClpLgBihw7yzK8N3n5zPaF3IMyErxfoNiXWH/3/A==",
+ "version": "5.83.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.83.1.tgz",
+ "integrity": "sha512-tdkpPFfzkTksN9BIlT/qjixSAtKrsW6PUVRwdKWaOcag7DrD1vpki3UzzdfMQGDRGeg1Ue1Dg+rcl5FJGembNg==",
"license": "MIT",
"dependencies": {
- "@typescript-eslint/utils": "^8.18.1"
+ "@typescript-eslint/utils": "^8.37.0"
},
"funding": {
"type": "github",
@@ -2502,10 +2503,169 @@
"eslint": "^8.57.0 || ^9.0.0"
}
},
+ "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/project-service": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.40.0.tgz",
+ "integrity": "sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==",
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.40.0",
+ "@typescript-eslint/types": "^8.40.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz",
+ "integrity": "sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.40.0",
+ "@typescript-eslint/visitor-keys": "8.40.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz",
+ "integrity": "sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==",
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/types": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.40.0.tgz",
+ "integrity": "sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==",
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz",
+ "integrity": "sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.40.0",
+ "@typescript-eslint/tsconfig-utils": "8.40.0",
+ "@typescript-eslint/types": "8.40.0",
+ "@typescript-eslint/visitor-keys": "8.40.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/utils": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.40.0.tgz",
+ "integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.40.0",
+ "@typescript-eslint/types": "8.40.0",
+ "@typescript-eslint/typescript-estree": "8.40.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.40.0.tgz",
+ "integrity": "sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==",
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.40.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@tanstack/eslint-plugin-query/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@tanstack/eslint-plugin-query/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@tanstack/query-core": {
- "version": "5.83.0",
- "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.83.0.tgz",
- "integrity": "sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==",
+ "version": "5.85.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.85.5.tgz",
+ "integrity": "sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2513,22 +2673,35 @@
}
},
"node_modules/@tanstack/query-devtools": {
- "version": "5.81.2",
- "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.81.2.tgz",
- "integrity": "sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==",
+ "version": "5.84.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.84.0.tgz",
+ "integrity": "sha512-fbF3n+z1rqhvd9EoGp5knHkv3p5B2Zml1yNRjh7sNXklngYI5RVIWUrUjZ1RIcEoscarUb0+bOvIs5x9dwzOXQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
- "node_modules/@tanstack/solid-query": {
- "version": "5.83.0",
- "resolved": "https://registry.npmjs.org/@tanstack/solid-query/-/solid-query-5.83.0.tgz",
- "integrity": "sha512-RF8Tv9+6+Kmzj+EafbTzvzzPq+J5SzHtc1Tz3D2MZ/EvlZTH+GL5q4HNnWK3emg7CB6WzyGnTuERmmWJaZs8/w==",
+ "node_modules/@tanstack/query-persist-client-core": {
+ "version": "5.85.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.85.5.tgz",
+ "integrity": "sha512-2JQiyiTVaaUu8pwPqOp6tjNa64ZN+0T9eZ3lfksV4le1VuG99fTcAYmZFIydvzwWlSM7GEF/1kpl5bwW2Y1qfQ==",
"license": "MIT",
"dependencies": {
- "@tanstack/query-core": "5.83.0"
+ "@tanstack/query-core": "5.85.5"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/solid-query": {
+ "version": "5.85.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/solid-query/-/solid-query-5.85.5.tgz",
+ "integrity": "sha512-0o0Ibk9wqydm4JatbIjmvDu1+MofeZ1bU9BKwAbpt7HYjrLVCeddpW6zGmp41nN7t/mHJyR+ctW9oiNumCkEfg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.85.5"
},
"funding": {
"type": "github",
@@ -2539,19 +2712,36 @@
}
},
"node_modules/@tanstack/solid-query-devtools": {
- "version": "5.83.0",
- "resolved": "https://registry.npmjs.org/@tanstack/solid-query-devtools/-/solid-query-devtools-5.83.0.tgz",
- "integrity": "sha512-Z0wQlAWXz/U2bJ/paMRBTDhMoPnB9Te6GmA21sXnI+nDnAAPZRcPxFBiCgYJS3eFsvbkdRGJwoUSQrdIgy0shg==",
+ "version": "5.85.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/solid-query-devtools/-/solid-query-devtools-5.85.5.tgz",
+ "integrity": "sha512-9rC22wILlV9Lcsi4xKPmzRkNio1NOxNT36diIS+HjpOmhsEP/aI8XkNKQa/KPhhaSN2naYaTCJamh7eBAQ0Ymg==",
"license": "MIT",
"dependencies": {
- "@tanstack/query-devtools": "5.81.2"
+ "@tanstack/query-devtools": "5.84.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
- "@tanstack/solid-query": "^5.83.0",
+ "@tanstack/solid-query": "^5.85.5",
+ "solid-js": "^1.6.0"
+ }
+ },
+ "node_modules/@tanstack/solid-query-persist-client": {
+ "version": "5.85.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/solid-query-persist-client/-/solid-query-persist-client-5.85.5.tgz",
+ "integrity": "sha512-2aG7UnLZlfE3R4XKqYuIeXVKjJOghjsjq4EU2Ifp915FTBZcZo61sEw1zRqRlrDjEFYAs4kJUZwqViDSJYyX2g==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-persist-client-core": "5.85.5"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "@tanstack/solid-query": "^5.85.5",
"solid-js": "^1.6.0"
}
},
@@ -2894,6 +3084,7 @@
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.36.0.tgz",
"integrity": "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.36.0",
@@ -2915,6 +3106,7 @@
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.36.0.tgz",
"integrity": "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.36.0",
@@ -2932,6 +3124,7 @@
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.36.0.tgz",
"integrity": "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2972,6 +3165,7 @@
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.36.0.tgz",
"integrity": "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2985,6 +3179,7 @@
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.36.0.tgz",
"integrity": "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.36.0",
@@ -3013,6 +3208,7 @@
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -3025,6 +3221,7 @@
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.36.0.tgz",
"integrity": "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
@@ -3048,6 +3245,7 @@
"version": "8.36.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.36.0.tgz",
"integrity": "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.36.0",
@@ -3065,6 +3263,7 @@
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
diff --git a/pkgs/clan-app/ui/package.json b/pkgs/clan-app/ui/package.json
index 93c386801..b60a38c0c 100644
--- a/pkgs/clan-app/ui/package.json
+++ b/pkgs/clan-app/ui/package.json
@@ -72,9 +72,10 @@
"@modular-forms/solid": "^0.25.1",
"@solid-primitives/storage": "^4.3.2",
"@solidjs/router": "^0.15.3",
- "@tanstack/eslint-plugin-query": "^5.51.12",
- "@tanstack/solid-query": "^5.76.0",
- "@tanstack/solid-query-devtools": "^5.83.0",
+ "@tanstack/eslint-plugin-query": "^5.83.1",
+ "@tanstack/solid-query": "^5.85.5",
+ "@tanstack/solid-query-devtools": "^5.85.5",
+ "@tanstack/solid-query-persist-client": "^5.85.5",
"solid-js": "^1.9.7",
"solid-toast": "^0.5.0",
"three": "^0.176.0",
diff --git a/pkgs/clan-app/ui/src/components/ListClansModal/ListClansModal.tsx b/pkgs/clan-app/ui/src/components/ListClansModal/ListClansModal.tsx
index 7a2a5c711..022a5d415 100644
--- a/pkgs/clan-app/ui/src/components/ListClansModal/ListClansModal.tsx
+++ b/pkgs/clan-app/ui/src/components/ListClansModal/ListClansModal.tsx
@@ -11,7 +11,7 @@ import { useClanListQuery } from "@/src/hooks/queries";
import { Alert } from "@/src/components/Alert/Alert";
export interface ListClansModalProps {
- onClose: () => void;
+ onClose?: () => void;
error?: {
title: string;
description: string;
@@ -68,7 +68,7 @@ export const ListClansModal = (props: ListClansModalProps) => {
size="s"
startIcon="Plus"
onClick={() => {
- props.onClose();
+ props.onClose?.();
navigateToOnboarding(navigate, true);
}}
>
diff --git a/pkgs/clan-app/ui/src/components/Modal/Modal.tsx b/pkgs/clan-app/ui/src/components/Modal/Modal.tsx
index cdca5d8c7..e69089fb1 100644
--- a/pkgs/clan-app/ui/src/components/Modal/Modal.tsx
+++ b/pkgs/clan-app/ui/src/components/Modal/Modal.tsx
@@ -30,7 +30,7 @@ export const useModalContext = () => {
export interface ModalProps {
id?: string;
title: string;
- onClose: () => void;
+ onClose?: () => void;
children: JSX.Element;
mount?: Node;
class?: string;
@@ -57,13 +57,11 @@ export const Modal = (props: ModalProps) => {
>
{props.title}
- {
- props.onClose();
- }}
- >
-
-
+
+
+
+
+
{(metaHeader) => (
diff --git a/pkgs/clan-app/ui/src/hooks/queries.ts b/pkgs/clan-app/ui/src/hooks/queries.ts
index 72817df99..97d58cd56 100644
--- a/pkgs/clan-app/ui/src/hooks/queries.ts
+++ b/pkgs/clan-app/ui/src/hooks/queries.ts
@@ -1,7 +1,14 @@
-import { useQueries, useQuery, UseQueryResult } from "@tanstack/solid-query";
+import {
+ QueryClient,
+ useQueries,
+ useQuery,
+ UseQueryResult,
+} from "@tanstack/solid-query";
import { SuccessData } from "../hooks/api";
import { encodeBase64 } from "@/src/hooks/clan";
import { useApiClient } from "./ApiClient";
+import { experimental_createQueryPersister } from "@tanstack/solid-query-persist-client";
+import { ClanDetailsStore } from "@/src/stores/clanDetails";
export type ClanDetails = SuccessData<"get_clan_details">;
export type ClanDetailsWithURI = ClanDetails & { uri: string };
@@ -24,6 +31,14 @@ export interface MachineDetail {
export type MachinesQueryResult = UseQueryResult;
export type ClanListQueryResult = UseQueryResult[];
+export const DefaultQueryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ },
+ },
+});
+
export const useMachinesQuery = (clanURI: string) => {
const client = useApiClient();
@@ -155,10 +170,15 @@ export const useMachineDetailsQuery = (
}));
};
+export const ClanDetailsPersister = experimental_createQueryPersister({
+ storage: ClanDetailsStore,
+});
+
export const useClanDetailsQuery = (clanURI: string) => {
const client = useApiClient();
return useQuery(() => ({
queryKey: ["clans", encodeBase64(clanURI), "details"],
+ persister: ClanDetailsPersister.persisterFn,
queryFn: async () => {
const call = client.fetch("get_clan_details", {
flake: {
@@ -169,7 +189,7 @@ export const useClanDetailsQuery = (clanURI: string) => {
if (result.status === "error") {
// todo should we create some specific error types?
- console.error("Error fetching clan details:", result.errors);
+ console.error("Error fetching clan details", clanURI, result.errors);
throw new Error(result.errors[0].message);
}
@@ -181,32 +201,54 @@ export const useClanDetailsQuery = (clanURI: string) => {
}));
};
-export const useClanListQuery = (clanURIs: string[]): ClanListQueryResult => {
+export const useClanListQuery = (
+ clanURIs: string[],
+ activeClanURI?: string,
+): ClanListQueryResult => {
const client = useApiClient();
+
return useQueries(() => ({
- queries: clanURIs.map((clanURI) => ({
- queryKey: ["clans", encodeBase64(clanURI), "details"],
- enabled: !!clanURI,
- queryFn: async () => {
- const call = client.fetch("get_clan_details", {
- flake: {
- identifier: clanURI,
- },
- });
- const result = await call.result;
+ queries: clanURIs.map((clanURI) => {
+ const queryKey = ["clans", encodeBase64(clanURI), "details"];
- if (result.status === "error") {
- // todo should we create some specific error types?
- console.error("Error fetching clan details:", result.errors);
- throw new Error(result.errors[0].message);
- }
+ return {
+ // eslint-disable-next-line @tanstack/query/exhaustive-deps
+ queryKey,
+ persister: ClanDetailsPersister.persisterFn,
+ queryFn: async () => {
+ // we only perform a request for the active clan
+ // for all others we load the cached query state
+ // this is due to how expensive it currently is to evaluate a flake for clan details
+ // it also helps when a clan folder has been moved/renamed
+ if (clanURI != activeClanURI) {
+ const cached = DefaultQueryClient.getQueryCache().find({
+ queryKey,
+ });
- return {
- uri: clanURI,
- ...result.data,
- };
- },
- })),
+ if (cached?.state?.data) {
+ return cached.state.data;
+ }
+ }
+
+ const call = client.fetch("get_clan_details", {
+ flake: {
+ identifier: clanURI,
+ },
+ });
+ const result = await call.result;
+
+ if (result.status === "error") {
+ // todo should we create some specific error types?
+ throw new Error(result.errors[0].message);
+ }
+
+ return {
+ uri: clanURI,
+ ...result.data,
+ };
+ },
+ };
+ }),
}));
};
diff --git a/pkgs/clan-app/ui/src/index.tsx b/pkgs/clan-app/ui/src/index.tsx
index 79c8de509..27056d870 100644
--- a/pkgs/clan-app/ui/src/index.tsx
+++ b/pkgs/clan-app/ui/src/index.tsx
@@ -2,21 +2,14 @@
import { render } from "solid-js/web";
import "./index.css";
-import { QueryClient, QueryClientProvider } from "@tanstack/solid-query";
+import { QueryClientProvider } from "@tanstack/solid-query";
import { Routes } from "@/src/routes";
import { Router } from "@solidjs/router";
import { Layout } from "@/src/routes/Layout";
import { SolidQueryDevtools } from "@tanstack/solid-query-devtools";
import { ApiClientProvider } from "./hooks/ApiClient";
import { callApi } from "./hooks/api";
-
-export const client = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- },
- },
-});
+import { DefaultQueryClient } from "@/src/hooks/queries";
const root = document.getElementById("app");
@@ -32,7 +25,7 @@ if (import.meta.env.DEV) {
render(
() => (
-
+
{import.meta.env.DEV && }
{Routes}
diff --git a/pkgs/clan-app/ui/src/routes/Clan/Clan.tsx b/pkgs/clan-app/ui/src/routes/Clan/Clan.tsx
index b26d41f54..23b9fa4f4 100644
--- a/pkgs/clan-app/ui/src/routes/Clan/Clan.tsx
+++ b/pkgs/clan-app/ui/src/routes/Clan/Clan.tsx
@@ -35,6 +35,7 @@ import { TextInput } from "@/src/components/Form/TextInput";
import { createForm, FieldValues, reset } from "@modular-forms/solid";
import { Sidebar } from "@/src/components/Sidebar/Sidebar";
import { UseQueryResult } from "@tanstack/solid-query";
+import { ListClansModal } from "@/src/components/ListClansModal/ListClansModal";
interface ClanContextProps {
clanURI: string;
@@ -44,16 +45,18 @@ interface ClanContextProps {
allClansQueries: UseQueryResult[];
isLoading(): boolean;
+ isError(): boolean;
}
class DefaultClanContext implements ClanContextProps {
public readonly clanURI: string;
- public readonly machinesQuery: MachinesQueryResult;
public readonly activeClanQuery: UseQueryResult;
public readonly otherClanQueries: UseQueryResult[];
public readonly allClansQueries: UseQueryResult[];
+ public readonly machinesQuery: MachinesQueryResult;
+
allQueries: UseQueryResult[];
constructor(
@@ -75,6 +78,10 @@ class DefaultClanContext implements ClanContextProps {
isLoading(): boolean {
return this.allQueries.some((q) => q.isLoading);
}
+
+ isError(): boolean {
+ return this.activeClanQuery.isError;
+ }
}
export const ClanContext = createContext();
@@ -83,8 +90,15 @@ export const Clan: Component = (props) => {
const clanURI = useClanURI();
const activeClanQuery = useClanDetailsQuery(clanURI);
+ createEffect(() => {
+ if (activeClanQuery.isError) {
+ console.error("Error loading active clan", activeClanQuery.error);
+ }
+ });
+
const otherClanQueries = useClanListQuery(
- clanURIs().filter((uri) => uri !== clanURI),
+ clanURIs().filter((uri) => uri != clanURI),
+ clanURI,
);
const machinesQuery = useMachinesQuery(clanURI);
@@ -164,7 +178,6 @@ const ClanSceneController = (props: RouteSectionProps) => {
}
const navigate = useNavigate();
- const { clanURI } = ctx;
const [dialogHandlers, setDialogHandlers] = createSignal<{
resolve: ({ id }: { id: string }) => void;
@@ -182,7 +195,7 @@ const ClanSceneController = (props: RouteSectionProps) => {
const api = callApi("create_machine", {
opts: {
clan_dir: {
- identifier: clanURI,
+ identifier: ctx.clanURI,
},
machine: {
name: values.name,
@@ -206,29 +219,38 @@ const ClanSceneController = (props: RouteSectionProps) => {
const [showModal, setShowModal] = createSignal(false);
+ const [loadingError, setLoadingError] = createSignal<
+ { title: string; description: string } | undefined
+ >();
const [loadingCooldown, setLoadingCooldown] = createSignal(false);
+
onMount(() => {
setTimeout(() => {
setLoadingCooldown(true);
}, 1500);
});
+ createEffect(() => {
+ if (ctx.activeClanQuery.isError) {
+ setLoadingError({
+ title: "Error loading clan",
+ description: ctx.activeClanQuery.error.message,
+ });
+ }
+ });
+
const [selectedIds, setSelectedIds] = createSignal>(new Set());
const onMachineSelect = (ids: Set) => {
// Get the first selected ID and navigate to its machine details
const selected = ids.values().next().value;
if (selected) {
- navigate(buildMachinePath(clanURI, selected));
+ navigate(buildMachinePath(ctx.clanURI, selected));
}
};
const machine = createMemo(() => maybeUseMachineName());
- createEffect(() => {
- console.log("Selected clan:", clanURI);
- });
-
createEffect(
on(machine, (machineId) => {
if (machineId) {
@@ -245,6 +267,9 @@ const ClanSceneController = (props: RouteSectionProps) => {
return (
<>
+
+
+
{
@@ -285,13 +310,13 @@ const ClanSceneController = (props: RouteSectionProps) => {
if (!s.sceneData) {
s.sceneData = {};
}
- if (!s.sceneData[clanURI]) {
- s.sceneData[clanURI] = {};
+ if (!s.sceneData[ctx.clanURI]) {
+ s.sceneData[ctx.clanURI] = {};
}
- if (!s.sceneData[clanURI][machineId]) {
- s.sceneData[clanURI][machineId] = { position: pos };
+ if (!s.sceneData[ctx.clanURI][machineId]) {
+ s.sceneData[ctx.clanURI][machineId] = { position: pos };
} else {
- s.sceneData[clanURI][machineId].position = pos;
+ s.sceneData[ctx.clanURI][machineId].position = pos;
}
}),
);
diff --git a/pkgs/clan-app/ui/src/stores/clan.ts b/pkgs/clan-app/ui/src/stores/clan.ts
index 3eaa591a3..f69c6b395 100644
--- a/pkgs/clan-app/ui/src/stores/clan.ts
+++ b/pkgs/clan-app/ui/src/stores/clan.ts
@@ -7,12 +7,18 @@ export interface ClanStoreType {
clanURIs: string[];
activeClanURI?: string;
sceneData: Record;
+ queryCache: {
+ clanDetails: Record;
+ };
}
const [store, setStore] = makePersisted(
createStore({
clanURIs: [],
sceneData: {},
+ queryCache: {
+ clanDetails: {},
+ },
}),
{
name: "clanStore",
diff --git a/pkgs/clan-app/ui/src/stores/clanDetails.ts b/pkgs/clan-app/ui/src/stores/clanDetails.ts
new file mode 100644
index 000000000..5e4c68c42
--- /dev/null
+++ b/pkgs/clan-app/ui/src/stores/clanDetails.ts
@@ -0,0 +1,32 @@
+import { produce } from "solid-js/store";
+import { AsyncStorage } from "@tanstack/query-persist-client-core";
+import { setStore, store } from "@/src/stores/clan";
+
+class ClanDetailsStoreImpl implements AsyncStorage {
+ entries() {
+ return Object.entries(store.queryCache.clanDetails);
+ }
+
+ getItem(key: string) {
+ return store.queryCache.clanDetails[key];
+ }
+
+ removeItem(key: string) {
+ setStore(
+ produce((state) => {
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+ delete state.queryCache.clanDetails[key];
+ }),
+ );
+ }
+
+ setItem(key: string, value: string) {
+ return setStore(
+ produce((state) => {
+ state.queryCache.clanDetails[key] = value;
+ }),
+ );
+ }
+}
+
+export const ClanDetailsStore = new ClanDetailsStoreImpl();