IMprovements hosting

This commit is contained in:
iamlukethedev
2026-04-11 22:01:17 -05:00
parent 5d9c35063f
commit ead4a6ffca
5 changed files with 155 additions and 238 deletions
+4
View File
@@ -86,3 +86,7 @@ test-results
# Local HTTPS development certificates (generated by dev:https).
.certs/
.understand-anything/
#Ignore cursor AI rules
.cursor/rules/codacy.mdc
+76
View File
@@ -0,0 +1,76 @@
import { NextRequest, NextResponse } from "next/server";
const ACCESS_TOKEN_COOKIE = "claw3d-access";
const ACCESS_TOKEN_PARAM = "access_token";
/**
* Middleware that protects hosted Claw3D workspaces with a token-based
* access gate. When CLAW3D_ACCESS_TOKEN is set (managed deployments),
* every request must carry a valid cookie. If the cookie is missing,
* the middleware checks for a query-string token and sets the cookie
* on match; otherwise it returns a 403 page.
*
* When CLAW3D_ACCESS_TOKEN is not set (local / self-hosted), the
* middleware is a no-op.
*/
export function middleware(request: NextRequest) {
const expectedToken = process.env.CLAW3D_ACCESS_TOKEN;
// Self-hosted mode: no gate.
if (!expectedToken) {
return NextResponse.next();
}
// Allow health checks and API routes used by the control plane.
const { pathname } = request.nextUrl;
if (
pathname.startsWith("/api/health") ||
pathname.startsWith("/api/managed") ||
pathname.startsWith("/_next") ||
pathname === "/favicon.ico"
) {
return NextResponse.next();
}
// Check cookie first.
const cookieToken = request.cookies.get(ACCESS_TOKEN_COOKIE)?.value;
if (cookieToken === expectedToken) {
return NextResponse.next();
}
// Accept token from query string and set a session cookie.
const queryToken = request.nextUrl.searchParams.get(ACCESS_TOKEN_PARAM);
if (queryToken === expectedToken) {
const cleanUrl = request.nextUrl.clone();
cleanUrl.searchParams.delete(ACCESS_TOKEN_PARAM);
const response = NextResponse.redirect(cleanUrl);
response.cookies.set(ACCESS_TOKEN_COOKIE, expectedToken, {
httpOnly: true,
secure: request.nextUrl.protocol === "https:",
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 30,
});
return response;
}
// No valid credential -- block access.
return new NextResponse(
`<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><title>Access required</title>
<style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#0a0a0a;color:#e5e5e5}
.box{text-align:center;max-width:420px;padding:2rem}.box h1{font-size:1.5rem;margin-bottom:.5rem}.box p{color:#999;font-size:.875rem}</style>
</head>
<body><div class="box"><h1>Access required</h1><p>Open this workspace from your Claw3D dashboard to authenticate.</p></div></body>
</html>`,
{
status: 403,
headers: { "Content-Type": "text/html; charset=utf-8" },
},
);
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon\\.ico).*)"],
};
+16 -231
View File
@@ -1,18 +1,17 @@
{
"name": "claw3d",
"version": "0.1.0",
"version": "0.1.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claw3d",
"version": "0.1.0",
"version": "0.1.5",
"license": "MIT",
"dependencies": {
"@noble/ed25519": "^3.0.0",
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.5.0",
"@vercel/otel": "^2.1.0",
"canvas-confetti": "^1.9.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -163,7 +162,6 @@
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.28.6",
"@babel/generator": "^7.28.6",
@@ -470,7 +468,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@@ -511,7 +508,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@@ -1959,141 +1955,6 @@
"node": ">=12.4.0"
}
},
"node_modules/@opentelemetry/api": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@opentelemetry/api-logs": {
"version": "0.211.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.211.0.tgz",
"integrity": "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/api": "^1.3.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@opentelemetry/core": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz",
"integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.29.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.0.0 <1.10.0"
}
},
"node_modules/@opentelemetry/instrumentation": {
"version": "0.211.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz",
"integrity": "sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/api-logs": "0.211.0",
"import-in-the-middle": "^2.0.0",
"require-in-the-middle": "^8.0.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.3.0"
}
},
"node_modules/@opentelemetry/resources": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz",
"integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.5.0",
"@opentelemetry/semantic-conventions": "^1.29.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.3.0 <1.10.0"
}
},
"node_modules/@opentelemetry/sdk-logs": {
"version": "0.211.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.211.0.tgz",
"integrity": "sha512-O5nPwzgg2JHzo59kpQTPUOTzFi0Nv5LxryG27QoXBciX3zWM3z83g+SNOHhiQVYRWFSxoWn1JM2TGD5iNjOwdA==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/api-logs": "0.211.0",
"@opentelemetry/core": "2.5.0",
"@opentelemetry/resources": "2.5.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.4.0 <1.10.0"
}
},
"node_modules/@opentelemetry/sdk-metrics": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz",
"integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.5.0",
"@opentelemetry/resources": "2.5.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.9.0 <1.10.0"
}
},
"node_modules/@opentelemetry/sdk-trace-base": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz",
"integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.5.0",
"@opentelemetry/resources": "2.5.0",
"@opentelemetry/semantic-conventions": "^1.29.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.3.0 <1.10.0"
}
},
"node_modules/@opentelemetry/semantic-conventions": {
"version": "1.39.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz",
"integrity": "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==",
"license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
"node_modules/@peculiar/asn1-cms": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz",
@@ -2259,7 +2120,6 @@
"integrity": "sha512-fWza+Lpbj6SkQKCrU6si4iu+fD2dD3gxNHFhUPxsfXBPhnv3rRSQVd0NtBUT9Z/RhF/boCBcuUaMUSTRTopjZg==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"playwright": "1.58.0"
},
@@ -2315,7 +2175,6 @@
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz",
"integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.17.8",
"@types/webxr": "*",
@@ -3069,6 +2928,7 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@@ -3089,6 +2949,7 @@
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"dequal": "^2.0.3"
}
@@ -3170,7 +3031,8 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/canvas-confetti": {
"version": "1.9.0",
@@ -3286,7 +3148,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz",
"integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -3297,7 +3158,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -3322,7 +3182,6 @@
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.183.1.tgz",
"integrity": "sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@dimforge/rapier3d-compat": "~0.12.0",
"@tweenjs/tween.js": "~23.1.3",
@@ -3400,7 +3259,6 @@
"integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.53.1",
"@typescript-eslint/types": "8.53.1",
@@ -3918,24 +3776,6 @@
"react": ">= 16.8.0"
}
},
"node_modules/@vercel/otel": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@vercel/otel/-/otel-2.1.0.tgz",
"integrity": "sha512-Zwu2Cu4t46DzBnY1DQSTxZ4MBLVfYsOjnlWuZuLRWnmVPX+SNrVHbs3ssiJ6uvY1J1JJswor4zSn8mHYxzYeBA==",
"license": "MIT",
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.9.0 <2.0.0",
"@opentelemetry/api-logs": ">=0.200.0 <0.300.0",
"@opentelemetry/instrumentation": ">=0.200.0 <0.300.0",
"@opentelemetry/resources": ">=2.0.0 <3.0.0",
"@opentelemetry/sdk-logs": ">=0.200.0 <0.300.0",
"@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0",
"@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0"
}
},
"node_modules/@vitest/expect": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
@@ -4057,8 +3897,8 @@
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -4066,15 +3906,6 @@
"node": ">=0.4.0"
}
},
"node_modules/acorn-import-attributes": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
"integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
"license": "MIT",
"peerDependencies": {
"acorn": "^8"
}
},
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -4118,6 +3949,7 @@
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=8"
}
@@ -4495,7 +4327,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -4724,18 +4555,11 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/cjs-module-lexer": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
"integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
"license": "MIT"
},
"node_modules/class-variance-authority": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"clsx": "^2.1.1"
},
@@ -4754,7 +4578,6 @@
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6"
}
@@ -5112,7 +4935,8 @@
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/draco3d": {
"version": "1.5.7",
@@ -5431,7 +5255,6 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -5633,7 +5456,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -6532,18 +6354,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/import-in-the-middle": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.5.tgz",
"integrity": "sha512-0InH9/4oDCBRzWXhpOqusspLBrVfK1vPvbn9Wxl8DAQ8yyx5fWJRETICSwkiAMaYntjJAMBP1R4B6cQnEUYVEA==",
"license": "Apache-2.0",
"dependencies": {
"acorn": "^8.15.0",
"acorn-import-attributes": "^1.9.5",
"cjs-module-lexer": "^2.2.0",
"module-details-from-path": "^1.0.4"
}
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -7148,7 +6958,6 @@
"integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@acemir/cssom": "^0.9.28",
"@asamuzakjp/dom-selector": "^6.7.6",
@@ -7631,6 +7440,7 @@
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
@@ -8599,12 +8409,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/module-details-from-path": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz",
"integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -9178,6 +8982,7 @@
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
@@ -9193,6 +8998,7 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
@@ -9205,7 +9011,8 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/promise-worker-transferable": {
"version": "1.0.4",
@@ -9295,7 +9102,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -9305,7 +9111,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -9518,19 +9323,6 @@
"node": ">=0.10.0"
}
},
"node_modules/require-in-the-middle": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz",
"integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.5",
"module-details-from-path": "^1.0.3"
},
"engines": {
"node": ">=9.3.0 || >=8.10.0 <9.0.0"
}
},
"node_modules/resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@@ -10292,7 +10084,6 @@
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
"integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
"license": "MIT",
"peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
@@ -10323,8 +10114,7 @@
"version": "0.183.2",
"resolved": "https://registry.npmjs.org/three/-/three-0.183.2.tgz",
"integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/three-mesh-bvh": {
"version": "0.8.3",
@@ -10416,7 +10206,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -10752,7 +10541,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -11026,7 +10814,6 @@
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -11135,7 +10922,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -11487,7 +11273,6 @@
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true,
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -5,7 +5,7 @@
* and content slots for each onboarding phase. Designed to be mounted
* at the app root and dismissed once complete or skipped.
*/
import { useCallback, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { ArrowLeft, ArrowRight, X } from "lucide-react";
import {
@@ -74,6 +74,31 @@ export const OnboardingWizard = ({
);
const [managedByokReady, setManagedByokReady] = useState(false);
// In managed mode, skip straight to "company" when BYOK is already configured.
useEffect(() => {
if (!managedDeployment) return;
let cancelled = false;
fetch("/api/managed/byok", { cache: "no-store" })
.then((r) => r.json())
.then((result) => {
if (cancelled) return;
const providers = Array.isArray(result.configuredProviders)
? result.configuredProviders
: [];
if (providers.length > 0) {
setCompletedSteps(new Set<OnboardingStepId>(["welcome", "prerequisites", "byok"]));
setCurrentStep("company");
setManagedByokReady(true);
}
})
.catch(() => {});
return () => {
cancelled = true;
};
}, [managedDeployment]);
const stepIndex = useMemo(
() => steps.findIndex((step) => step.id === currentStep),
[currentStep, steps],
+33 -6
View File
@@ -6,6 +6,7 @@ import { createDefaultAgentAvatarProfile } from "@/lib/avatars/profile";
import {
AGENT_SCALE,
WALK_ANIM_SPEED,
WALK_SPEED,
} from "@/features/retro-office/core/constants";
import { toWorld } from "@/features/retro-office/core/geometry";
import type {
@@ -97,7 +98,25 @@ export const AgentModel = memo(function AgentModel({
: undefined;
const workoutStyle = agent.workoutStyle ?? "lift";
const frameValue = agent.frame + (agent.phaseOffset ?? 0) / WALK_ANIM_SPEED;
const walkPhase = Math.sin(frameValue * WALK_ANIM_SPEED);
const walkSpeedRatio = THREE.MathUtils.clamp(
(agent.walkSpeed ?? WALK_SPEED) / WALK_SPEED,
1,
4,
);
const walkCycle = frameValue * WALK_ANIM_SPEED * (0.8 + walkSpeedRatio * 0.12);
const walkPhase = Math.sin(walkCycle);
const walkLiftPhase = Math.abs(Math.sin(walkCycle));
const walkSwing = THREE.MathUtils.clamp(
0.42 + (walkSpeedRatio - 1) * 0.08,
0.42,
0.68,
);
const walkLegSwing = THREE.MathUtils.clamp(walkSwing + 0.14, 0.56, 0.82);
const walkLean = THREE.MathUtils.clamp(
0.08 + (walkSpeedRatio - 1) * 0.018,
0.08,
0.14,
);
const workoutPhase = Math.sin(
agent.frame * 0.18 + (agent.phaseOffset ?? 0),
);
@@ -108,6 +127,8 @@ export const AgentModel = memo(function AgentModel({
groupRef.current.rotation.x =
agent.state === "sitting"
? -0.15
: agent.state === "walking"
? walkLean
: isDancing
? Math.sin(agent.frame * 0.18 + (agent.phaseOffset ?? 0)) * 0.06
: isWorkout
@@ -127,7 +148,7 @@ export const AgentModel = memo(function AgentModel({
: 0;
const bounce =
agent.state === "walking"
? Math.sin(frameValue * WALK_ANIM_SPEED) * 0.04
? walkLiftPhase * 0.07
: isDancing
? 0.03 +
Math.abs(Math.sin(agent.frame * 0.22 + (agent.phaseOffset ?? 0))) *
@@ -153,7 +174,10 @@ export const AgentModel = memo(function AgentModel({
leftArmRef.current.rotation.x = -0.22;
leftArmRef.current.rotation.z = -0.08;
} else if (agent.state === "walking") {
leftArmRef.current.rotation.x = walkPhase * 0.4;
leftArmRef.current.rotation.x = walkPhase * walkSwing - 0.12;
leftArmRef.current.rotation.y = -0.04;
leftArmRef.current.rotation.z = -0.1 - walkLiftPhase * 0.05;
groupRef.current.rotation.z = Math.sin(walkCycle * 2) * 0.025;
} else if (isDancing) {
leftArmRef.current.rotation.x =
-0.8 + Math.sin(agent.frame * 0.22) * 0.9;
@@ -212,7 +236,10 @@ export const AgentModel = memo(function AgentModel({
rightArmRef.current.rotation.y = 0.18;
rightArmRef.current.rotation.z = 0.08;
} else if (agent.state === "walking") {
rightArmRef.current.rotation.x = -walkPhase * 0.4;
rightArmRef.current.rotation.x = -walkPhase * walkSwing - 0.12;
rightArmRef.current.rotation.y = 0.04;
rightArmRef.current.rotation.z = 0.1 + walkLiftPhase * 0.05;
groupRef.current.rotation.z = Math.sin(walkCycle * 2) * 0.025;
} else if (isDancing) {
rightArmRef.current.rotation.x =
-0.8 - Math.sin(agent.frame * 0.22) * 0.9;
@@ -265,7 +292,7 @@ export const AgentModel = memo(function AgentModel({
if (leftLegRef.current) {
leftLegRef.current.rotation.x =
agent.state === "walking"
? walkPhase * 0.35
? walkPhase * walkLegSwing + walkLiftPhase * 0.08
: isDancing
? Math.sin(agent.frame * 0.22 + (agent.phaseOffset ?? 0)) * 0.35
: isWorkout
@@ -285,7 +312,7 @@ export const AgentModel = memo(function AgentModel({
if (rightLegRef.current) {
rightLegRef.current.rotation.x =
agent.state === "walking"
? -walkPhase * 0.35
? -walkPhase * walkLegSwing + walkLiftPhase * 0.08
: isDancing
? -Math.sin(agent.frame * 0.22 + (agent.phaseOffset ?? 0)) * 0.35
: isWorkout