wizards 0.2.18 → 0.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/apps/runtime/dist/app.js +2 -1
  2. package/apps/runtime/dist/connectors/external.js +4 -1
  3. package/apps/runtime/dist/manage.js +4 -0
  4. package/apps/runtime/dist/mcp/auth.js +4 -0
  5. package/apps/runtime/dist/mcp/handler.js +2 -1
  6. package/apps/runtime/dist/routes/api.js +12 -9
  7. package/apps/runtime/dist/routes/internal.js +17 -1
  8. package/apps/runtime/dist/routes/studio.js +55 -7
  9. package/apps/runtime/dist/services/access.js +25 -0
  10. package/apps/runtime/dist/services/errors.js +1 -0
  11. package/apps/runtime/dist/services/package.js +2 -0
  12. package/apps/runtime/dist/services/projects.js +15 -2
  13. package/apps/runtime/dist/services/wizards.js +4 -1
  14. package/apps/web/dist/assets/{Account-Cp1cgqjV.js → Account-0f8W571o.js} +1 -1
  15. package/apps/web/dist/assets/{ChangelogDialog-Chke1qUe.js → ChangelogDialog-BBmJedTW.js} +1 -1
  16. package/apps/web/dist/assets/{ChatPanel-DWrwewr6.js → ChatPanel-Bi3tnxOD.js} +1 -1
  17. package/apps/web/dist/assets/{CreditsDialog-Ckema8Ss.js → CreditsDialog-C_lMgenE.js} +1 -1
  18. package/apps/web/dist/assets/EditorPage-2-DypFBB.js +2 -0
  19. package/apps/web/dist/assets/{FlowPreview-DS545n78.js → FlowPreview-DBroqouz.js} +1 -1
  20. package/apps/web/dist/assets/{Harness-H3yXP-2L.js → Harness-Dkw5Zldw.js} +1 -1
  21. package/apps/web/dist/assets/HomePage-D9f7DKEw.js +1 -0
  22. package/apps/web/dist/assets/{ImportWizard-0_NkA8aS.js → ImportWizard-Br-Ys-Np.js} +1 -1
  23. package/apps/web/dist/assets/{LocalRuntime-DnABzTOe.js → LocalRuntime-CbA3kdFH.js} +1 -1
  24. package/apps/web/dist/assets/NewWizardPage-Cvt9fKwq.js +2 -0
  25. package/apps/web/dist/assets/ProjectPage-CWn7fwSr.js +5 -0
  26. package/apps/web/dist/assets/{PublicRunner-CtZ7vxl_.js → PublicRunner-DFUM0aP5.js} +1 -1
  27. package/apps/web/dist/assets/{RunnerView-xV5bWclu.js → RunnerView-BAmePlD3.js} +2 -2
  28. package/apps/web/dist/assets/SettingsPage-CLLlnzeS.js +1 -0
  29. package/apps/web/dist/assets/{SetupPage-c6mMD9zN.js → SetupPage-GWL_IhbR.js} +1 -1
  30. package/apps/web/dist/assets/{SharePage-DTKIbMj-.js → SharePage-DZzIMn0m.js} +1 -1
  31. package/apps/web/dist/assets/{api-CVG-xDEn.js → api-Byy4NRvQ.js} +1 -1
  32. package/apps/web/dist/assets/changelog-DMAoeotb.js +1 -0
  33. package/apps/web/dist/assets/{esm-DLM3jdbm.js → esm-WstF63Ly.js} +1 -1
  34. package/apps/web/dist/assets/index-udMtduDM.js +10 -0
  35. package/apps/web/dist/assets/{meta-m947x6uG.js → meta-CUWuppZ0.js} +1 -1
  36. package/apps/web/dist/assets/{settings-menu-DUY6__C3.js → settings-menu-GUFlmH3n.js} +1 -1
  37. package/apps/web/dist/assets/ui-CwU0VR_B.js +1 -0
  38. package/apps/web/dist/index.html +3 -3
  39. package/apps/web/dist/mcp/flow.html +1 -1
  40. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +13 -1
  41. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +3 -0
  42. package/package.json +1 -1
  43. package/apps/web/dist/assets/EditorPage-XX6pfHk5.js +0 -2
  44. package/apps/web/dist/assets/HomePage-Bds84ShK.js +0 -1
  45. package/apps/web/dist/assets/NewWizardPage-BqP75V9K.js +0 -2
  46. package/apps/web/dist/assets/ProjectPage-DbOvwOdZ.js +0 -5
  47. package/apps/web/dist/assets/SettingsPage-DKt55zFh.js +0 -1
  48. package/apps/web/dist/assets/changelog-CXXT0zsp.js +0 -1
  49. package/apps/web/dist/assets/index-1gmaxDSk.js +0 -10
  50. package/apps/web/dist/assets/ui-BPY2pIh0.js +0 -1
@@ -25,6 +25,7 @@ import { connectCallback, publicRoutes, runRoutes, shareRoutes } from "./routes/
25
25
  import { studio } from "./routes/studio.js";
26
26
  import { wizardStream } from "./routes/wizard-stream.js";
27
27
  import { runTicketValid } from "./secrets/signing.js";
28
+ import { asRole } from "./services/access.js";
28
29
  import { ServiceError } from "./services/errors.js";
29
30
  import { tenantOfLink, tenantStatus } from "./tenants/control.js";
30
31
  const app = new Hono();
@@ -158,7 +159,7 @@ app.use("/api/studio/*", async (c, next) => {
158
159
  return c.json({ error: "Dieses Konto ist gesperrt.", code: "suspended" }, 403);
159
160
  }
160
161
  c.set("user", user);
161
- return withTenant(user.tenantId, next);
162
+ return withTenant(user.tenantId, () => asRole(user.role, next));
162
163
  });
163
164
  const mcp = mcpHandler();
164
165
  app.on(["GET", "POST", "DELETE"], "/api/mcp", (c) => mcp(c.req.raw));
@@ -9,7 +9,7 @@ import { discoverImportableSources, discoverOAuthFacts, registryDiscover, regist
9
9
  import { resolveRegistrySource } from "../engenty/connections-external/registry-source.js";
10
10
  import { encryptToken } from "../engenty/shims/connections-sdk.js";
11
11
  import { env } from "../env.js";
12
- import { requireWritableProject } from "../services/access.js";
12
+ import { requireAdmin, requireWritableProject } from "../services/access.js";
13
13
  import { ServiceError } from "../services/errors.js";
14
14
  import { BUILTIN_CONNECTORS, builtinConnector, missingSetup, usable } from "./builtin.js";
15
15
  /**
@@ -272,6 +272,7 @@ export async function resolveConnector(projectId, id) {
272
272
  }
273
273
  export async function importConnector(userId, projectId, input) {
274
274
  await requireWritableProject(projectId);
275
+ await requireAdmin();
275
276
  if (await load(projectId, input.id)) {
276
277
  throw new ServiceError("invalid", `Connector "${input.id}" already exists in this project.`);
277
278
  }
@@ -314,6 +315,7 @@ export async function importConnector(userId, projectId, input) {
314
315
  */
315
316
  export async function importFromRegistry(userId, projectId, input) {
316
317
  await requireWritableProject(projectId);
318
+ await requireAdmin();
317
319
  const service = await registryService(input.domain);
318
320
  const order = input.kind ? [input.kind] : ["mcp", "openapi"];
319
321
  const source = order
@@ -338,6 +340,7 @@ export async function importFromRegistry(userId, projectId, input) {
338
340
  }
339
341
  export async function removeConnector(projectId, id) {
340
342
  await requireWritableProject(projectId);
343
+ await requireAdmin();
341
344
  await db.delete(schema.projectConnector).where(where(projectId, id));
342
345
  }
343
346
  /**
@@ -100,6 +100,10 @@ async function call(method, path, body) {
100
100
  }
101
101
  return data;
102
102
  }
103
+ /** The people of a tenant, as the Manage-App keeps them; they are managed there. */
104
+ export function tenantMembers(id) {
105
+ return call("GET", `/v1/tenants/${encodeURIComponent(id)}/members`);
106
+ }
103
107
  const tenants = new Map();
104
108
  /** Plan, limits and balance of a tenant, cached for a minute. */
105
109
  export async function tenantInfo(id, fresh = false) {
@@ -47,6 +47,7 @@ async function apiKeyAuth(key) {
47
47
  const who = {
48
48
  userId: "local",
49
49
  tenantId: LOCAL_TENANT,
50
+ role: "owner",
50
51
  client: found.name || "MCP",
51
52
  scopes: [...SCOPES],
52
53
  };
@@ -64,6 +65,7 @@ async function apiKeyAuth(key) {
64
65
  const who = {
65
66
  userId: result.userId,
66
67
  tenantId: result.tenantId,
68
+ role: result.role ?? "member",
67
69
  client: result.name || "MCP",
68
70
  scopes: [...SCOPES],
69
71
  };
@@ -83,6 +85,7 @@ async function tokenAuth(token) {
83
85
  const who = {
84
86
  userId: claims.userId,
85
87
  tenantId: claims.tenantId,
88
+ role: claims.role,
86
89
  client: claims.clientId || "MCP",
87
90
  scopes: SCOPES.filter((s) => granted.has(s)),
88
91
  };
@@ -104,6 +107,7 @@ function localAuth(request, ticket) {
104
107
  const who = {
105
108
  userId: "local",
106
109
  tenantId: LOCAL_TENANT,
110
+ role: "owner",
107
111
  client,
108
112
  scopes: [...SCOPES],
109
113
  };
@@ -1,6 +1,7 @@
1
1
  import { createMcpHandler, McpServer, originValidationResponse, } from "@modelcontextprotocol/server";
2
2
  import { withTenant } from "../db/client.js";
3
3
  import { env } from "../env.js";
4
+ import { asRole } from "../services/access.js";
4
5
  import { tenantStatus } from "../tenants/control.js";
5
6
  import { authenticate, principalOf } from "./auth.js";
6
7
  import { registerFlowApp } from "./flow-app.js";
@@ -47,6 +48,6 @@ export function mcpHandler() {
47
48
  .catch(() => null);
48
49
  noteRequest(who.tenantId, authInfo.clientId, who.client, body);
49
50
  }
50
- return withTenant(who.tenantId, () => handler.fetch(request, { authInfo }));
51
+ return withTenant(who.tenantId, () => asRole(who.role, () => handler.fetch(request, { authInfo })));
51
52
  };
52
53
  }
@@ -4,6 +4,7 @@ import { z } from "zod";
4
4
  import { withTenant } from "../db/client.js";
5
5
  import { allowed } from "../limits.js";
6
6
  import { authenticate, principalOf } from "../mcp/auth.js";
7
+ import { asRole } from "../services/access.js";
7
8
  import { ServiceError } from "../services/errors.js";
8
9
  import { deleteFile, draftFiles, writeFile } from "../services/files.js";
9
10
  import { checkOnServer, checkSchema, listSyncedSpaces, patchSyncedWizard, removeSyncedSpace, removeSyncedWizard, rotateSyncedLink, syncDataSchema, syncSettingsSchema, syncSpaceData, syncWizard, syncWizardSchema, } from "../services/spaces.js";
@@ -20,6 +21,8 @@ const importSchema = z.object({
20
21
  .default([]),
21
22
  publish: z.boolean().default(false),
22
23
  });
24
+ /** Runs a call in the caller's tenant, as the caller's role. */
25
+ const inTenantAs = (who, fn) => withTenant(who.tenantId, () => asRole(who.role, fn));
23
26
  const HOUR = 60 * 60 * 1000;
24
27
  /** A wizard with its workspace: 25 MB of files as base64, and the definition. */
25
28
  const WIZARD_BYTES = 48 * 1024 * 1024;
@@ -62,7 +65,7 @@ export const apiRoutes = new Hono()
62
65
  if (who instanceof Response) {
63
66
  return who;
64
67
  }
65
- return withTenant(who.tenantId, async () => c.json({ spaces: await listSyncedSpaces() }));
68
+ return inTenantAs(who, async () => c.json({ spaces: await listSyncedSpaces() }));
66
69
  })
67
70
  .post("/spaces/check", async (c) => {
68
71
  const who = await caller(c, ["wizards:read"]);
@@ -70,7 +73,7 @@ export const apiRoutes = new Hono()
70
73
  return who;
71
74
  }
72
75
  const body = checkSchema.parse(await c.req.json());
73
- return withTenant(who.tenantId, async () => c.json({ problems: await checkOnServer(body) }));
76
+ return inTenantAs(who, async () => c.json({ problems: await checkOnServer(body) }));
74
77
  })
75
78
  .put("/spaces/:spaceId/wizards/:wizardId", tooLarge, async (c) => {
76
79
  const who = await caller(c, ["wizards:write", "wizards:publish"]);
@@ -82,7 +85,7 @@ export const apiRoutes = new Hono()
82
85
  return limited;
83
86
  }
84
87
  const body = syncWizardSchema.parse(await c.req.json());
85
- return withTenant(who.tenantId, async () => c.json(await syncWizard(c.req.param("spaceId"), c.req.param("wizardId"), body)));
88
+ return inTenantAs(who, async () => c.json(await syncWizard(c.req.param("spaceId"), c.req.param("wizardId"), body)));
86
89
  })
87
90
  // The space's own tables and pages, sent with a wizard when they changed.
88
91
  .put("/spaces/:spaceId/data", tooLarge, async (c) => {
@@ -95,7 +98,7 @@ export const apiRoutes = new Hono()
95
98
  return limited;
96
99
  }
97
100
  const body = syncDataSchema.parse(await c.req.json());
98
- return withTenant(who.tenantId, async () => c.json(await syncSpaceData(c.req.param("spaceId"), body)));
101
+ return inTenantAs(who, async () => c.json(await syncSpaceData(c.req.param("spaceId"), body)));
99
102
  })
100
103
  .patch("/spaces/:spaceId/wizards/:wizardId", async (c) => {
101
104
  const who = await caller(c, ["wizards:publish"]);
@@ -107,7 +110,7 @@ export const apiRoutes = new Hono()
107
110
  return limited;
108
111
  }
109
112
  const body = syncSettingsSchema.parse(await c.req.json());
110
- return withTenant(who.tenantId, async () => c.json(await patchSyncedWizard(c.req.param("spaceId"), c.req.param("wizardId"), body)));
113
+ return inTenantAs(who, async () => c.json(await patchSyncedWizard(c.req.param("spaceId"), c.req.param("wizardId"), body)));
111
114
  })
112
115
  .post("/spaces/:spaceId/wizards/:wizardId/rotate-link", async (c) => {
113
116
  const who = await caller(c, ["wizards:publish"]);
@@ -118,21 +121,21 @@ export const apiRoutes = new Hono()
118
121
  if (limited) {
119
122
  return limited;
120
123
  }
121
- return withTenant(who.tenantId, async () => c.json(await rotateSyncedLink(who.userId, c.req.param("spaceId"), c.req.param("wizardId"))));
124
+ return inTenantAs(who, async () => c.json(await rotateSyncedLink(who.userId, c.req.param("spaceId"), c.req.param("wizardId"))));
122
125
  })
123
126
  .delete("/spaces/:spaceId/wizards/:wizardId", async (c) => {
124
127
  const who = await caller(c, ["wizards:write"]);
125
128
  if (who instanceof Response) {
126
129
  return who;
127
130
  }
128
- return withTenant(who.tenantId, async () => c.json(await removeSyncedWizard(who.userId, c.req.param("spaceId"), c.req.param("wizardId"))));
131
+ return inTenantAs(who, async () => c.json(await removeSyncedWizard(who.userId, c.req.param("spaceId"), c.req.param("wizardId"))));
129
132
  })
130
133
  .delete("/spaces/:spaceId", async (c) => {
131
134
  const who = await caller(c, ["wizards:write"]);
132
135
  if (who instanceof Response) {
133
136
  return who;
134
137
  }
135
- return withTenant(who.tenantId, async () => c.json(await removeSyncedSpace(c.req.param("spaceId"))));
138
+ return inTenantAs(who, async () => c.json(await removeSyncedSpace(c.req.param("spaceId"))));
136
139
  })
137
140
  .post("/wizards/import", tooLarge, async (c) => {
138
141
  const authInfo = await authenticate(c.req.raw);
@@ -148,7 +151,7 @@ export const apiRoutes = new Hono()
148
151
  if ((await tenantStatus(who.tenantId)) === "suspended") {
149
152
  return c.json({ error: "tenant_suspended" }, 403);
150
153
  }
151
- return withTenant(who.tenantId, async () => {
154
+ return inTenantAs(who, async () => {
152
155
  const writer = { source: "mcp", client: who.client };
153
156
  let wizardId = body.wizardId;
154
157
  if (wizardId) {
@@ -7,6 +7,7 @@ import { closeTenantDb, tenantDb } from "../db/client.js";
7
7
  import { env } from "../env.js";
8
8
  import { removeTenantObjects } from "../files/objects.js";
9
9
  import { forgetTenant, managed } from "../manage.js";
10
+ import { loadedPlugins } from "../plugins/registry.js";
10
11
  import { forgetTenantRows, setTenantStatus } from "../tenants/control.js";
11
12
  function fromManage(header) {
12
13
  const given = Buffer.from(header?.replace(/^Bearer\s+/i, "") ?? "");
@@ -21,7 +22,22 @@ function fromManage(header) {
21
22
  * removes the tenant's files and everything the control database knows; the tenant's database
22
23
  * itself is deleted by whoever created it (the Manage-App, or here when it is a file).
23
24
  */
24
- export const internalRoutes = new Hono().post("/tenants/:id", async (c) => {
25
+ export const internalRoutes = new Hono()
26
+ // The plugins this runtime carries: the Manage-App offers them when a plan's modules are picked.
27
+ .get("/plugins", (c) => {
28
+ if (!fromManage(c.req.header("authorization"))) {
29
+ return c.json({ error: "unauthorized" }, 401);
30
+ }
31
+ return c.json({
32
+ plugins: loadedPlugins().map((p) => ({
33
+ id: p.source.id,
34
+ name: p.source.manifest.name ?? p.source.id,
35
+ description: p.source.manifest.description ?? "",
36
+ version: p.source.manifest.version ?? null,
37
+ })),
38
+ });
39
+ })
40
+ .post("/tenants/:id", async (c) => {
25
41
  if (!fromManage(c.req.header("authorization"))) {
26
42
  return c.json({ error: "unauthorized" }, 401);
27
43
  }
@@ -22,13 +22,13 @@ import { env } from "../env.js";
22
22
  import { freshAuth, loginEnv } from "../harness/env.js";
23
23
  import { detectHarness, detectHarnesses, HARNESS_IDS, harness, installSpec, } from "../harness/index.js";
24
24
  import { closeTerminal, openTerminal, resizeTerminal, subscribeTerminal, writeTerminal, } from "../harness/terminal.js";
25
- import { managed } from "../manage.js";
25
+ import { managed, tenantInfo, tenantMembers } from "../manage.js";
26
26
  import { seenApps } from "../mcp/seen.js";
27
27
  import { generateImageMedia, generateSpeechMedia, generateVideoMedia } from "../media/generate.js";
28
28
  import { transcribeAudio } from "../media/transcribe.js";
29
29
  import { checkKey, classProblem, embeddingModel, hasTextModel, LOCAL_KEYS, localModelSettings, ModelUnavailableError, modelSettings, saveLocalModels, saveModels, storedKey, textModel, } from "../models.js";
30
30
  import { HTML_RESPONSE_CSP } from "../render/guard.js";
31
- import { mayBuild, projectWritable, requireWritable } from "../services/access.js";
31
+ import { isAdminRole, mayBuild, mayCreate, projectWritable, requireWritable, } from "../services/access.js";
32
32
  import { architectTurn } from "../services/architect.js";
33
33
  import { checkForCloud, cloudLinked, cloudSpaces, cloudState, forgetCloud, publishAndSync, pushSharing, removeFromCloud, replaceCloudSpaces, rotateInCloud, syncToCloud, } from "../services/cloud.js";
34
34
  import { notFound, ServiceError } from "../services/errors.js";
@@ -39,7 +39,7 @@ import { exportWizard, importWizard, PACKAGE_EXTENSION, PACKAGE_MAX_BYTES, PACKA
39
39
  import { profileSchema, saveProfile, userProfile } from "../services/profile.js";
40
40
  import { addProjectFile, fileView, orderProjectFiles, projectFile, projectFileContent, projectFiles, reindexProjectFile, removeProjectFile, updateProjectFile, } from "../services/project-files.js";
41
41
  import { documentText, queueIndex, searchKnowledge } from "../services/project-index.js";
42
- import { createProject, deleteProject, listProjects, maskedServers, ownedProject, projectLimit, projectPatchSchema, updateProject, } from "../services/projects.js";
42
+ import { createProject, deleteProject, listProjects, maskedServers, ownedProject, projectLimitOrNull, projectPatchSchema, updateProject, } from "../services/projects.js";
43
43
  import { listResults, listRuns, startTestRun } from "../services/runs.js";
44
44
  import { addCategoryValue, categoryRow, categoryValue, deleteCategory, deleteCategoryValue, mergeCategoryValues, orderCategoryValues, putCategory, renameCategoryValue, setItemCategory, updateCategory, } from "../services/space-categories.js";
45
45
  import { addTableRow, createPage, createTable, deletePage, deleteTable, deleteTableRows, getPage, getTable, listSpaceData, orderSubPages, updatePage, updateTable, updateTableRow, } from "../services/space-data.js";
@@ -47,6 +47,7 @@ import { draftWidgetPreview } from "../services/widgets.js";
47
47
  import { createWizard, deleteWizard, duplicateWizard, listWizards, ownedWizard, rotateShareLink, updateWizardSettings, wizardMessages, wizardState, writableWizard, writeDraft, } from "../services/wizards.js";
48
48
  import { readSetting, writeSetting } from "../settings.js";
49
49
  import { codeOf } from "../tenants/control.js";
50
+ import { currentTenant } from "../tenants/tenant.js";
50
51
  import { applyUpdate, updateStatus } from "../update.js";
51
52
  import { byteRange } from "./delivery.js";
52
53
  /** The model sources a test call has worked on: the setup is done for those, not for the others. */
@@ -186,11 +187,20 @@ export const studio = new Hono()
186
187
  const linked = managed ? null : await linkedAccount();
187
188
  const overview = linked ? await accountOverview() : null;
188
189
  const profile = await userProfile(user);
190
+ // What the Manage-App says of the tenant: its plan and feature switches.
191
+ const info = managed ? await tenantInfo(user.tenantId).catch(() => null) : null;
189
192
  return c.json({
190
193
  user: { id: user.id, name: profile.name, email: profile.email, image: user.image ?? null },
191
194
  /** What the person says about themselves; name and e-mail are the account's when managed. */
192
195
  profile,
193
- tenant: { id: user.tenantId, role: user.role },
196
+ tenant: {
197
+ id: user.tenantId,
198
+ role: user.role,
199
+ /** The plan the team is on; null alone, or where the Manage-App names none. */
200
+ plan: info?.plan ?? null,
201
+ },
202
+ /** The plan's feature switches (`ownKeys`, …); alone, everything is on. */
203
+ features: managed ? (info?.features ?? {}) : { ownKeys: true },
194
204
  mode: managed ? "managed" : "local",
195
205
  /** The tenant's balance; null where the runtime resolves models itself. */
196
206
  credits: managed ? await balanceCredits() : null,
@@ -220,10 +230,36 @@ export const studio = new Hono()
220
230
  aiReady: (await hasTextModel()) || (!managed && (await subscriptionClients()).length > 0),
221
231
  mcpUrl: `${env.appUrl}/api/mcp`,
222
232
  /**
223
- * One project: the studio shows no project switcher. `build: false`: nothing is made or
224
- * changed here; the studio shows what the person's local install synced.
233
+ * One project: the studio shows no project switcher; null: as many as wanted. `build:
234
+ * false`: nothing is made or changed here; the studio shows what the person's local
235
+ * install synced. `create`: the person makes and deletes here (builds, and is no mere
236
+ * member). `members`: the people the plan allows, null for no limit.
225
237
  */
226
- limits: { projects: await projectLimit(), build: await mayBuild() },
238
+ limits: {
239
+ projects: await projectLimitOrNull(),
240
+ build: await mayBuild(),
241
+ create: await mayCreate(),
242
+ members: info?.limits.members ?? null,
243
+ },
244
+ });
245
+ })
246
+ // The team as the Manage-App keeps it: its people, who is invited, the places the plan gives.
247
+ .get("/team", async (c) => {
248
+ if (!managed) {
249
+ return c.json({ error: "not_managed" }, 404);
250
+ }
251
+ const user = c.get("user");
252
+ const [info, team] = await Promise.all([
253
+ tenantInfo(user.tenantId).catch(() => null),
254
+ tenantMembers(currentTenant()),
255
+ ]);
256
+ return c.json({
257
+ ...team,
258
+ plan: info?.plan ?? null,
259
+ /** Where members are invited and roles changed: the account pages of the Manage-App. */
260
+ manageUrl: env.manage.url,
261
+ /** Whether this person manages the team there. */
262
+ canManage: isAdminRole(user.role),
227
263
  });
228
264
  })
229
265
  // A runtime that runs alone says when a newer release is out and can run the update itself.
@@ -976,6 +1012,18 @@ export const studio = new Hono()
976
1012
  Object.values(input.bindings ?? {}).some((ref) => ref && !cloudBinding(ref)))) {
977
1013
  return c.json({ error: "Not available in the cloud." }, 400);
978
1014
  }
1015
+ // Own keys and bindings to a provider's model are a feature of the team's plan.
1016
+ if (managed) {
1017
+ const info = await tenantInfo(c.get("user").tenantId).catch(() => null);
1018
+ const own = Object.values(input.keys ?? {}).some((key) => key?.trim()) ||
1019
+ Object.values(input.bindings ?? {}).some((ref) => ref && !isCreditsRef(ref));
1020
+ if (own && info && info.features?.ownKeys === false) {
1021
+ return c.json({
1022
+ error: `Eigene API Keys gibt es nicht im Paket ${info.plan?.name ?? "des Teams"}.`,
1023
+ code: "plan",
1024
+ }, 403);
1025
+ }
1026
+ }
979
1027
  await saveModels(input);
980
1028
  return c.json(await modelSettings());
981
1029
  })
@@ -17,6 +17,31 @@ export function asSync(fn) {
17
17
  return sync.run(true, fn);
18
18
  }
19
19
  const syncing = () => sync.getStore() === true;
20
+ // --- roles -----------------------------------------------------------------------------
21
+ // In a team, an owner or admin makes and deletes (wizards, spaces, connectors); a member edits
22
+ // the wizards there are. A runtime that runs alone has one person, who does everything.
23
+ const actor = new AsyncLocalStorage();
24
+ /** Runs a request as the person's role in the tenant; set once per entry, like the tenant. */
25
+ export function asRole(role, fn) {
26
+ return actor.run(role, fn);
27
+ }
28
+ /** The role of who acts now. A context without one (a job, a sync) acts as the owner. */
29
+ export function currentRole() {
30
+ return actor.getStore() ?? "owner";
31
+ }
32
+ export const isAdminRole = (role) => role === "owner" || role === "admin";
33
+ export const MEMBERS_EDIT_ONLY = "Das dürfen nur Admins des Teams. Als Mitglied bearbeitest du die Wizards, die es gibt.";
34
+ /** Refuses a member: making and deleting is for the owner and the admins of the team. */
35
+ export async function requireAdmin() {
36
+ if (syncing() || !managed || isAdminRole(currentRole())) {
37
+ return;
38
+ }
39
+ throw new ServiceError("forbidden", MEMBERS_EDIT_ONLY);
40
+ }
41
+ /** Whether the person makes and deletes here: builds at all, and is no mere member. */
42
+ export async function mayCreate() {
43
+ return (await mayBuild()) && (!managed || isAdminRole(currentRole()));
44
+ }
20
45
  /** Whether the tenant builds wizards on this runtime. A runtime that runs alone always does. */
21
46
  export async function mayBuild() {
22
47
  if (!managed) {
@@ -6,6 +6,7 @@ const STATUS = {
6
6
  no_credits: 402,
7
7
  refused: 400,
8
8
  read_only: 403,
9
+ forbidden: 403,
9
10
  };
10
11
  export class ServiceError extends Error {
11
12
  code;
@@ -3,6 +3,7 @@ import { strFromU8, strToU8, unzipSync, zipSync } from "fflate";
3
3
  import { z } from "zod";
4
4
  import { listConnectors } from "../connectors/external.js";
5
5
  import { getBlob } from "../files/blobs.js";
6
+ import { requireAdmin } from "./access.js";
6
7
  import { ServiceError } from "./errors.js";
7
8
  import { draftFiles, writeFile } from "./files.js";
8
9
  import { ownedProject } from "./projects.js";
@@ -176,6 +177,7 @@ async function missing(userId, projectId, draft, manifest) {
176
177
  }
177
178
  /** Makes a new wizard of a package in the project: the draft and its workspace, not published. */
178
179
  export async function importWizard(userId, projectId, data, name) {
180
+ await requireAdmin();
179
181
  const { manifest, files } = readPackage(data);
180
182
  // The shape is checked before anything is written; a wrong one leaves no wizard behind.
181
183
  const { draft } = parseDraft(manifest.definition, files.map((f) => f.path));
@@ -7,7 +7,7 @@ import { env } from "../env.js";
7
7
  import { managed, tenantInfo } from "../manage.js";
8
8
  import { spaceChanged } from "../plugins/events.js";
9
9
  import { currentTenant } from "../tenants/tenant.js";
10
- import { mayBuild, requireBuild, requireWritable } from "./access.js";
10
+ import { mayBuild, requireAdmin, requireBuild, requireWritable } from "./access.js";
11
11
  import { notFound, ServiceError } from "./errors.js";
12
12
  import { forgetWizardLinks } from "./links.js";
13
13
  import { removeProjectFiles } from "./project-files.js";
@@ -48,12 +48,21 @@ export const projectPatchSchema = z.object({
48
48
  export async function projectLimit() {
49
49
  if (managed) {
50
50
  const info = await tenantInfo(currentTenant()).catch(() => null);
51
+ // Null: the plan sets no limit.
52
+ if (info && info.limits.projects === null) {
53
+ return Number.POSITIVE_INFINITY;
54
+ }
51
55
  if (info?.limits.projects) {
52
56
  return info.limits.projects;
53
57
  }
54
58
  }
55
59
  return Math.max(1, env.limits.projects);
56
60
  }
61
+ /** The number for the studio: null where there is no limit (JSON has no infinity). */
62
+ export async function projectLimitOrNull() {
63
+ const limit = await projectLimit();
64
+ return Number.isFinite(limit) ? limit : null;
65
+ }
57
66
  export async function ownedProject(_userId, projectId) {
58
67
  const p = await db.query.project.findFirst({
59
68
  where: and(eq(schema.project.id, projectId), eq(schema.project.tenantId, currentTenant())),
@@ -109,10 +118,11 @@ export async function listProjects(_userId) {
109
118
  await ensureProject();
110
119
  }
111
120
  // The oldest come first: with a limit of one, that is the project everything lands in.
121
+ const limit = await projectLimit();
112
122
  const projects = await db.query.project.findMany({
113
123
  where: build ? eq(schema.project.tenantId, currentTenant()) : synced(),
114
124
  orderBy: [schema.project.createdAt],
115
- limit: await projectLimit(),
125
+ ...(Number.isFinite(limit) ? { limit } : {}),
116
126
  });
117
127
  const counts = await db
118
128
  .select({ projectId: schema.wizard.projectId, n: count() })
@@ -135,6 +145,7 @@ export function maskedServers(p) {
135
145
  }
136
146
  export async function createProject(_userId, name) {
137
147
  await requireBuild();
148
+ await requireAdmin();
138
149
  const [{ n }] = await db
139
150
  .select({ n: count() })
140
151
  .from(schema.project)
@@ -153,6 +164,7 @@ export async function createProject(_userId, name) {
153
164
  export async function updateProject(userId, projectId, patch) {
154
165
  const p = await ownedProject(userId, projectId);
155
166
  await requireWritable(p);
167
+ await requireAdmin();
156
168
  const mcpServers = patch.mcpServers?.map((s) => {
157
169
  const before = p.mcpServers.find((x) => x.id === s.id);
158
170
  const headers = s.headers
@@ -188,6 +200,7 @@ export function projectProfile(p) {
188
200
  }
189
201
  export async function deleteProject(userId, projectId) {
190
202
  await requireWritable(await ownedProject(userId, projectId));
203
+ await requireAdmin();
191
204
  const all = await db.query.project.findMany({ where: madeHere() });
192
205
  if (all.length <= 1) {
193
206
  throw new ServiceError("refused", "Das letzte Projekt bleibt.");
@@ -8,7 +8,7 @@ import { env } from "../env.js";
8
8
  import { pluginToolsOf } from "../plugins/registry.js";
9
9
  import { dropLinks, putLink } from "../tenants/control.js";
10
10
  import { currentTenant } from "../tenants/tenant.js";
11
- import { requireBuild, requireWritable, requireWritableProject } from "./access.js";
11
+ import { requireAdmin, requireBuild, requireWritable, requireWritableProject } from "./access.js";
12
12
  import { changedSteps, emitDraftChanged } from "./draft-events.js";
13
13
  import { notFound, ServiceError } from "./errors.js";
14
14
  import { copyFiles, draftFiles, sameFiles, seedFiles } from "./files.js";
@@ -202,6 +202,7 @@ export function shapeIssues(error, raw) {
202
202
  export async function createWizard(userId, input, writer = { source: "studio" }) {
203
203
  // Asked first: a tenant that builds nothing here has no project of its own to name.
204
204
  await requireBuild();
205
+ await requireAdmin();
205
206
  const project = input.projectId
206
207
  ? await ownedProject(userId, input.projectId)
207
208
  : await defaultProject(userId);
@@ -330,6 +331,7 @@ export async function rotateShareLink(userId, wizardId) {
330
331
  return { shareToken: token, shareUrl: shareUrl(token) };
331
332
  }
332
333
  export async function duplicateWizard(userId, wizardId) {
334
+ await requireAdmin();
333
335
  const w = await writableWizard(userId, wizardId);
334
336
  const id = nanoid(12);
335
337
  const draft = { ...w.draft, title: `${w.draft.title} (Kopie)` };
@@ -349,6 +351,7 @@ export async function duplicateWizard(userId, wizardId) {
349
351
  return { id };
350
352
  }
351
353
  export async function deleteWizard(userId, wizardId) {
354
+ await requireAdmin();
352
355
  await writableWizard(userId, wizardId);
353
356
  await forgetWizardLinks(wizardId);
354
357
  await db
@@ -1 +1 @@
1
- import{a as e,n as t,t as n}from"./jsx-runtime-D3jfb0Ew.js";import{c as r,j as i,n as a,nt as o}from"./api-CVG-xDEn.js";import{t as s}from"./createLucideIcon-q3MZMmnf.js";import{t as c}from"./check-CUpby_J9.js";import{n as l,r as u}from"./upload-DNRENWzQ.js";import{t as d}from"./external-link-MAL7vJYj.js";import{b as f,c as p,d as m,h,m as g,n as _,o as v,r as y,s as b,t as x,v as S}from"./ui-BPY2pIh0.js";import{M as ee,R as C,U as w,f as T,o as te,p as E,u as D,x as O}from"./index-1gmaxDSk.js";import{n as k}from"./LocalRuntime-DnABzTOe.js";var A={name:`hard-drive`,size:24,node:[[`path`,{d:`M10 16h.01`,key:`1bzywj`}],[`path`,{d:`M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z`,key:`18tbho`}],[`path`,{d:`M21.946 12.013H2.054`,key:`zqlbp7`}],[`path`,{d:`M6 16h.01`,key:`1pmjb7`}]]};A.node;var j=s(A),M={name:`layout-grid`,size:24,node:[[`rect`,{width:`7`,height:`7`,x:`3`,y:`3`,rx:`1`,key:`1g98yp`}],[`rect`,{width:`7`,height:`7`,x:`14`,y:`3`,rx:`1`,key:`6d4xhi`}],[`rect`,{width:`7`,height:`7`,x:`14`,y:`14`,rx:`1`,key:`nxv5o0`}],[`rect`,{width:`7`,height:`7`,x:`3`,y:`14`,rx:`1`,key:`1bb6yr`}]]};M.node;var N=s(M),P={name:`list`,size:24,node:[[`path`,{d:`M3 5h.01`,key:`18ugdj`}],[`path`,{d:`M3 12h.01`,key:`nlz23k`}],[`path`,{d:`M3 19h.01`,key:`noohij`}],[`path`,{d:`M8 5h13`,key:`1pao27`}],[`path`,{d:`M8 12h13`,key:`1za7za`}],[`path`,{d:`M8 19h13`,key:`m83p4d`}]]};P.node;var F=s(P),I=e(t(),1),L=e=>[`project-files`,e];function R(e){return i({queryKey:L(e),queryFn:()=>a.get(`/api/studio/projects/${e}/files`),refetchInterval:e=>e.state.data?.files.some(e=>e.status===`pending`)?2e3:!1})}function z(e,t){return r(`/api/studio/projects/${e}/files/${t}/content`)}function B(e,t=!1){let n=o(),[r,i]=(0,I.useState)(!1),[s,c]=(0,I.useState)(null),l=()=>n.invalidateQueries({queryKey:L(e)}),u=async e=>{c(null);try{await e()}catch(e){c(e.message)}finally{await l()}};return{busy:r,error:s,readOnly:t,upload:async(t,n)=>{i(!0),await u(async()=>{for(let r of n)await a.upload(`/api/studio/projects/${e}/files?kind=${t}`,r),await l()}),i(!1)},describe:(t,n)=>u(()=>a.patch(`/api/studio/projects/${e}/files/${t}`,{description:n})),remove:t=>u(()=>a.del(`/api/studio/projects/${e}/files/${t}`)),reindex:t=>u(()=>a.post(`/api/studio/projects/${e}/files/${t}/reindex`)),order:(t,n)=>u(()=>a.put(`/api/studio/projects/${e}/files/order`,{kind:t,ids:n}))}}function V(e,t){let n=`wz.layout.${e}`,[r,i]=(0,I.useState)(()=>{try{let e=localStorage.getItem(n);return e===`list`||e===`cards`?e:t}catch{return t}});return[r,()=>i(e=>{let t=e===`list`?`cards`:`list`;try{localStorage.setItem(n,t)}catch{}return t})]}function H(e,t,n=700){let[r,i]=(0,I.useState)(`idle`),[a,o]=(0,I.useState)(null),s=JSON.stringify(e),c=(0,I.useRef)(s),l=(0,I.useRef)({value:e,json:s,save:t});l.current={value:e,json:s,save:t};let u=(0,I.useRef)(Promise.resolve()),d=(0,I.useCallback)(()=>{let e=l.current;e.json!==c.current&&(c.current=e.json,i(`saving`),u.current=u.current.then(async()=>{try{await e.save(e.value),o(null),i(`saved`)}catch(e){c.current=``,o(e.message),i(`error`)}}))},[]);return(0,I.useEffect)(()=>{if(s===c.current)return;let e=setTimeout(d,n);return()=>clearTimeout(e)},[s,n,d]),(0,I.useEffect)(()=>d,[d]),{state:r,error:a}}function U(e){return e<1e6?`${Math.max(1,Math.round(e/1e3))} KB`:`${(e/1e6).toFixed(1)} MB`}var W=e(n(),1);function G({id:e,children:t}){return(0,W.jsx)(`div`,{id:e,className:`scroll-mt-24 max-md:scroll-mt-36`,children:t})}function K({title:e,hint:t,save:n,action:r,locked:i,plain:a,children:o}){return(0,W.jsxs)(`section`,{className:`flex flex-col gap-2.5`,children:[(0,W.jsxs)(`div`,{className:`flex items-start justify-between gap-4 px-1`,children:[(0,W.jsxs)(`div`,{className:`min-w-0`,children:[(0,W.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,W.jsx)(`h2`,{className:`font-display font-semibold text-lg leading-tight`,children:e}),n?.state===`saving`?(0,W.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 text-[0.75rem] text-ink-3`,children:[(0,W.jsx)(m,{className:`size-3`}),` `,f(`project.saving`)]}):n?.state===`saved`?(0,W.jsxs)(`span`,{className:`inline-flex items-center gap-1 text-[0.75rem] text-moss`,children:[(0,W.jsx)(c,{className:`size-3.5`}),` `,f(`settings.saved`)]}):null]}),t?(0,W.jsx)(`p`,{className:`mt-1 text-[0.875rem] text-ink-3 leading-snug`,children:t}):null]}),r?(0,W.jsx)(`div`,{className:`shrink-0`,children:r}):null]}),a?o:(0,W.jsxs)(_,{className:`p-3.5 sm:p-5`,children:[i?(0,W.jsx)(`fieldset`,{disabled:!0,className:`min-w-0`,children:o}):o,n?.state===`error`&&n.error?(0,W.jsx)(`p`,{className:`mt-4 text-[0.875rem] text-rose`,children:n.error}):null]})]})}function q({children:e,layout:t,onToggle:n}){return(0,W.jsxs)(`div`,{className:`mb-3 flex items-start gap-2`,children:[(0,W.jsx)(`div`,{className:`min-w-0 flex-1`,children:(0,W.jsx)(`div`,{className:`w-56 max-w-full transition-[width] duration-200 focus-within:w-full has-[input:not(:placeholder-shown)]:w-full`,children:e})}),(0,W.jsx)(v,{label:f(t===`list`?`project.viewCards`:`project.viewList`),onClick:n,children:t===`list`?(0,W.jsx)(N,{className:`size-4`}):(0,W.jsx)(F,{className:`size-4`})})]})}function J({value:e,onChange:t,onEnter:n,placeholder:r,busy:i}){return(0,W.jsxs)(`div`,{className:`relative`,children:[(0,W.jsx)(l,{className:`-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 size-4 text-ink-4`}),(0,W.jsx)(`input`,{type:`search`,value:e,placeholder:r,onChange:e=>t(e.target.value),onKeyDown:e=>e.key===`Enter`&&n?.(),className:`h-9 w-full rounded-lg border border-input bg-card pr-3 pl-9 text-[0.875rem] text-ink outline-none transition placeholder:text-ink-4 focus:border-focus focus:ring-4 focus:ring-focus-glow`}),i?(0,W.jsx)(m,{className:`-translate-y-1/2 absolute top-1/2 right-3 size-4 text-ink-3`}):null]})}function Y({onFiles:e,accept:t,multiple:n=!0,className:r,children:i}){return(0,W.jsxs)(`label`,{onDragOver:e=>{e.preventDefault(),e.currentTarget.dataset.over=`1`},onDragLeave:e=>{delete e.currentTarget.dataset.over},onDrop:t=>{t.preventDefault(),delete t.currentTarget.dataset.over;let r=Array.from(t.dataTransfer.files);r.length&&e(n?r:r.slice(0,1))},className:h(`flex cursor-pointer items-center justify-center gap-2 rounded-lg border border-input border-dashed text-[0.875rem] text-ink-3 transition focus-within:border-focus focus-within:ring-4 focus-within:ring-focus-glow hover:border-ink-4 hover:text-ink data-[over]:border-focus data-[over]:bg-paper-2 data-[over]:text-ink`,r),children:[i,(0,W.jsx)(`input`,{className:`sr-only`,type:`file`,accept:t,multiple:n,onChange:t=>{let n=Array.from(t.target.files??[]);t.target.value=``,n.length&&e(n)}})]})}var X=`mt-1.5 text-[0.8125rem] text-ink-3`;function Z(e){let t=o(),[n,r]=(0,I.useState)(!1),i=w({mutationFn:e=>a.post(`/api/studio/account/link`,e),onSuccess:({url:e})=>{k(e),r(!0)}});return(0,I.useEffect)(()=>{if(!n)return;let e=setInterval(()=>void t.invalidateQueries({queryKey:[`me`]}),2e3),i=setTimeout(()=>r(!1),3e5);return()=>{clearInterval(e),clearTimeout(i)}},[n,t]),(0,I.useEffect)(()=>{e&&r(!1)},[e]),{link:i,waiting:n}}function Q({link:e,waiting:t}){return e.isError?(0,W.jsx)(`p`,{className:`text-[0.8125rem] text-rose`,children:f(`local.accountFailed`)}):t?(0,W.jsxs)(`p`,{className:`flex items-center gap-2 text-[0.8125rem] text-ink-3`,children:[(0,W.jsx)(m,{className:`size-3.5`}),` `,f(`local.accountWaiting`)]}):null}function ne(){let e=Z(!1),{link:t}=e,[n,r]=(0,I.useState)(``);return(0,W.jsxs)(`div`,{className:`flex flex-col divide-y divide-border-soft`,children:[(0,W.jsxs)(`div`,{className:`flex items-start gap-4 py-5 first:pt-0 last:pb-0`,children:[(0,W.jsx)(`div`,{className:`flex size-11 shrink-0 items-center justify-center rounded-full bg-paper-2 text-ink-2`,children:(0,W.jsx)(j,{className:`size-5`})}),(0,W.jsxs)(`div`,{className:`min-w-0`,children:[(0,W.jsx)(`div`,{className:`font-medium text-[0.9375rem]`,children:f(`account.local`)}),(0,W.jsx)(`p`,{className:`mt-1 text-[0.875rem] text-ink-3 leading-relaxed`,children:f(`account.localText`)})]})]}),E.account?(0,W.jsxs)(`div`,{className:`flex flex-col gap-4 py-5 last:pb-0`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`font-medium text-[0.9375rem]`,children:f(`account.with`)}),(0,W.jsxs)(`ul`,{className:`mt-2 flex flex-col gap-2 text-[0.875rem] text-ink-2 leading-snug`,children:[(0,W.jsxs)(`li`,{className:`flex items-start gap-2.5`,children:[(0,W.jsx)(u,{className:`mt-0.5 size-4 shrink-0 text-amber`}),f(`account.withCredits`)]}),(0,W.jsxs)(`li`,{className:`flex items-start gap-2.5`,children:[(0,W.jsx)(C,{className:`mt-0.5 size-4 shrink-0 text-cobalt`}),f(`account.withCloud`)]})]})]}),(0,W.jsx)(`div`,{children:(0,W.jsx)(x,{busy:t.isPending&&!t.variables?.signup,onClick:()=>t.mutate({}),children:f(`account.signIn`)})})]}):null,E.account?(0,W.jsxs)(`form`,{className:`flex flex-col gap-3 py-5 last:pb-0`,onSubmit:e=>{e.preventDefault(),t.mutate({signup:!0,code:n.trim()||void 0})},children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`font-medium text-[0.9375rem]`,children:f(`account.new`)}),(0,W.jsx)(`p`,{className:`mt-1 text-[0.875rem] text-ink-3 leading-relaxed`,children:f(`account.newHint`)})]}),(0,W.jsxs)(`div`,{className:`flex flex-wrap gap-2`,children:[(0,W.jsx)(b,{value:n,maxLength:64,autoComplete:`off`,spellCheck:!1,placeholder:f(`account.code`),"aria-label":f(`account.code`),onChange:e=>r(e.target.value),className:`w-56 font-mono text-[0.875rem]`}),(0,W.jsx)(x,{type:`submit`,variant:`secondary`,className:`h-11`,busy:t.isPending&&!!t.variables?.signup,children:f(`account.create`)})]}),(0,W.jsx)(Q,{...e})]}):null]})}var $={start:`account.kind.start`,monthly:`account.kind.monthly`,gift:`account.kind.gift`};function re({account:e}){let t=o(),n=Z(e.signedIn),r=w({mutationFn:()=>a.del(`/api/studio/account`),onSuccess:()=>t.invalidateQueries({queryKey:[`me`]})}),i=new Intl.DateTimeFormat(S,{dateStyle:`medium`});return(0,W.jsxs)(`div`,{className:`flex flex-col divide-y divide-border-soft`,children:[(0,W.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-4 gap-y-3 py-5 first:pt-0`,children:[(0,W.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,W.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,W.jsx)(`span`,{className:`truncate font-medium text-[0.9375rem]`,children:e.name||e.email}),e.signedIn?(0,W.jsx)(y,{tone:`live`,children:f(`account.linked`)}):null]}),(0,W.jsx)(`div`,{className:`truncate text-[0.875rem] text-ink-3`,children:e.email})]}),(0,W.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,W.jsxs)(x,{variant:`secondary`,onClick:()=>k(e.url),children:[f(`setup.openAccount`),` `,(0,W.jsx)(d,{className:`size-4`})]}),(0,W.jsxs)(x,{variant:`ghost`,busy:r.isPending,onClick:()=>r.mutate(),children:[(0,W.jsx)(ee,{className:`size-4`}),` `,f(`nav.logout`)]})]})]}),e.signedIn?null:(0,W.jsxs)(`div`,{className:`flex flex-col gap-3 py-5`,children:[(0,W.jsx)(`p`,{className:`text-[0.875rem] text-ink-2`,children:f(`local.accountExpired`)}),(0,W.jsx)(`div`,{children:(0,W.jsx)(x,{busy:n.link.isPending,onClick:()=>n.link.mutate({}),children:f(`account.signInAgain`)})}),(0,W.jsx)(Q,{...n})]}),e.credits===null?null:(0,W.jsxs)(`div`,{className:`py-5`,children:[(0,W.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-4 gap-y-3`,children:[(0,W.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,W.jsx)(`div`,{className:`text-[0.8125rem] text-ink-3`,children:f(`account.credits`)}),(0,W.jsx)(`div`,{className:`font-display font-semibold text-[1.375rem] tabular-nums leading-tight`,children:f(`nav.credits`,{n:Math.floor(e.credits).toLocaleString()})})]}),(0,W.jsxs)(x,{variant:`secondary`,onClick:()=>k(`${e.url}/billing`),children:[f(`account.topUp`),` `,(0,W.jsx)(d,{className:`size-4`})]})]}),e.expiring.length?(0,W.jsx)(`ul`,{className:`mt-3 flex flex-col gap-1 text-[0.8125rem] text-ink-3 tabular-nums`,children:e.expiring.map(e=>(0,W.jsxs)(`li`,{children:[f(`account.expires`,{n:Math.floor(e.credits).toLocaleString(),date:i.format(new Date(e.expiresAt))}),$[e.kind]?(0,W.jsxs)(`span`,{className:`text-ink-4`,children:[` · `,f($[e.kind])]}):null]},`${e.kind}:${e.expiresAt}`))}):null]}),(0,W.jsxs)(`div`,{className:`flex items-start gap-2.5 py-5 text-[0.875rem] text-ink-2 leading-snug last:pb-0`,children:[(0,W.jsx)(C,{className:`mt-0.5 size-4 shrink-0 text-cobalt`}),(0,W.jsx)(`p`,{children:f(`account.cloud`,{host:te(e.cloudUrl)})})]})]})}function ie({me:e,fromAccount:t}){let n=o(),[r,i]=(0,I.useState)(e.profile),s=e=>i(t=>({...t,...e})),c=H(r,async e=>{await a.put(`/api/studio/profile`,e),await n.invalidateQueries({queryKey:[`me`]})});return(0,W.jsx)(K,{title:f(`account.profile`),hint:f(`account.hint`),save:c,children:(0,W.jsxs)(`div`,{className:`flex flex-col gap-6 sm:flex-row sm:gap-8`,children:[(0,W.jsxs)(`div`,{className:`flex shrink-0 flex-col items-center gap-2 sm:w-32`,children:[(0,W.jsx)(`div`,{className:`flex size-20 items-center justify-center overflow-hidden rounded-full bg-ember-tint font-semibold text-[1.75rem] text-ember-strong ring-1 ring-border-soft`,children:e.user.image?(0,W.jsx)(`img`,{src:e.user.image,alt:``,className:`size-full object-cover`}):O(r.name)||`?`}),(0,W.jsx)(`p`,{className:`text-center text-[0.75rem] text-ink-3 leading-snug`,children:f(`account.initials`)})]}),(0,W.jsxs)(`div`,{className:`flex min-w-0 flex-1 flex-col gap-5`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(p,{children:f(`account.name`)}),(0,W.jsx)(b,{value:r.name,maxLength:80,disabled:t,onChange:e=>s({name:e.target.value})}),(0,W.jsx)(`p`,{className:X,children:f(t?`account.fromAccount`:`account.nameHint`)})]}),(0,W.jsxs)(`div`,{children:[(0,W.jsx)(p,{children:f(`account.about`)}),(0,W.jsx)(g,{minRows:3,maxLength:2e3,value:r.about,onChange:e=>s({about:e.target.value})}),(0,W.jsx)(`p`,{className:X,children:f(`account.aboutHint`)})]}),(0,W.jsxs)(`div`,{className:`grid gap-5 sm:grid-cols-2`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(p,{children:f(`account.email`)}),(0,W.jsx)(b,{type:`email`,value:r.email,maxLength:200,disabled:t,onChange:e=>s({email:e.target.value})})]}),(0,W.jsxs)(`div`,{children:[(0,W.jsx)(p,{children:f(`account.phone`)}),(0,W.jsx)(b,{type:`tel`,value:r.phone,maxLength:40,onChange:e=>s({phone:e.target.value})})]})]})]})]})})}function ae({me:e}){let t=e.mode===`managed`,n=t||!!e.account;return(0,W.jsxs)(`div`,{className:`flex flex-col gap-9`,children:[(0,W.jsx)(K,{title:f(`account.connection`),hint:f(`account.connectionHint`),children:t?(0,W.jsxs)(`div`,{className:`flex flex-wrap items-center gap-4`,children:[(0,W.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,W.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,W.jsx)(`span`,{className:`truncate font-medium text-[0.9375rem]`,children:e.user.name}),(0,W.jsx)(y,{tone:`live`,children:f(`account.linked`)})]}),(0,W.jsx)(`div`,{className:`truncate text-[0.875rem] text-ink-3`,children:e.user.email})]}),e.manageUrl?(0,W.jsxs)(x,{variant:`secondary`,onClick:()=>k(`${e.manageUrl}/account`),children:[f(`setup.openAccount`),` `,(0,W.jsx)(d,{className:`size-4`})]}):null]}):e.account?(0,W.jsx)(re,{account:e.account}):(0,W.jsx)(ne,{})}),(0,W.jsx)(ie,{me:e,fromAccount:n},n?`account`:`own`),(0,W.jsx)(K,{title:f(`account.look`),hint:f(`account.lookHint`),children:(0,W.jsxs)(`div`,{className:`flex flex-col divide-y divide-border-soft`,children:[(0,W.jsxs)(`div`,{className:`flex items-center justify-between gap-4 pb-4`,children:[(0,W.jsx)(`span`,{className:`font-medium text-[0.875rem]`,children:f(`nav.language`)}),(0,W.jsx)(T,{tone:`surface`})]}),(0,W.jsxs)(`div`,{className:`flex items-center justify-between gap-4 pt-4`,children:[(0,W.jsx)(`span`,{className:`font-medium text-[0.875rem]`,children:f(`nav.theme`)}),(0,W.jsx)(D,{})]})]})})]})}export{Y as a,K as c,H as d,B as f,N as h,G as i,U as l,R as m,Q as n,q as o,V as p,Z as r,J as s,ae as t,z as u};
1
+ import{a as e,n as t,t as n}from"./jsx-runtime-D3jfb0Ew.js";import{c as r,j as i,n as a,nt as o}from"./api-Byy4NRvQ.js";import{t as s}from"./createLucideIcon-q3MZMmnf.js";import{t as c}from"./check-CUpby_J9.js";import{n as l,r as u}from"./upload-DNRENWzQ.js";import{t as d}from"./external-link-MAL7vJYj.js";import{b as f,c as p,d as m,h,m as g,n as _,o as v,r as y,s as b,t as x,v as S}from"./ui-CwU0VR_B.js";import{F as ee,K as C,V as w,f as T,o as te,p as E,u as D,x as O}from"./index-udMtduDM.js";import{n as k}from"./LocalRuntime-CbA3kdFH.js";var A={name:`hard-drive`,size:24,node:[[`path`,{d:`M10 16h.01`,key:`1bzywj`}],[`path`,{d:`M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z`,key:`18tbho`}],[`path`,{d:`M21.946 12.013H2.054`,key:`zqlbp7`}],[`path`,{d:`M6 16h.01`,key:`1pmjb7`}]]};A.node;var j=s(A),M={name:`layout-grid`,size:24,node:[[`rect`,{width:`7`,height:`7`,x:`3`,y:`3`,rx:`1`,key:`1g98yp`}],[`rect`,{width:`7`,height:`7`,x:`14`,y:`3`,rx:`1`,key:`6d4xhi`}],[`rect`,{width:`7`,height:`7`,x:`14`,y:`14`,rx:`1`,key:`nxv5o0`}],[`rect`,{width:`7`,height:`7`,x:`3`,y:`14`,rx:`1`,key:`1bb6yr`}]]};M.node;var N=s(M),P={name:`list`,size:24,node:[[`path`,{d:`M3 5h.01`,key:`18ugdj`}],[`path`,{d:`M3 12h.01`,key:`nlz23k`}],[`path`,{d:`M3 19h.01`,key:`noohij`}],[`path`,{d:`M8 5h13`,key:`1pao27`}],[`path`,{d:`M8 12h13`,key:`1za7za`}],[`path`,{d:`M8 19h13`,key:`m83p4d`}]]};P.node;var F=s(P),I=e(t(),1),L=e=>[`project-files`,e];function R(e){return i({queryKey:L(e),queryFn:()=>a.get(`/api/studio/projects/${e}/files`),refetchInterval:e=>e.state.data?.files.some(e=>e.status===`pending`)?2e3:!1})}function z(e,t){return r(`/api/studio/projects/${e}/files/${t}/content`)}function B(e,t=!1){let n=o(),[r,i]=(0,I.useState)(!1),[s,c]=(0,I.useState)(null),l=()=>n.invalidateQueries({queryKey:L(e)}),u=async e=>{c(null);try{await e()}catch(e){c(e.message)}finally{await l()}};return{busy:r,error:s,readOnly:t,upload:async(t,n)=>{i(!0),await u(async()=>{for(let r of n)await a.upload(`/api/studio/projects/${e}/files?kind=${t}`,r),await l()}),i(!1)},describe:(t,n)=>u(()=>a.patch(`/api/studio/projects/${e}/files/${t}`,{description:n})),remove:t=>u(()=>a.del(`/api/studio/projects/${e}/files/${t}`)),reindex:t=>u(()=>a.post(`/api/studio/projects/${e}/files/${t}/reindex`)),order:(t,n)=>u(()=>a.put(`/api/studio/projects/${e}/files/order`,{kind:t,ids:n}))}}function V(e,t){let n=`wz.layout.${e}`,[r,i]=(0,I.useState)(()=>{try{let e=localStorage.getItem(n);return e===`list`||e===`cards`?e:t}catch{return t}});return[r,()=>i(e=>{let t=e===`list`?`cards`:`list`;try{localStorage.setItem(n,t)}catch{}return t})]}function H(e,t,n=700){let[r,i]=(0,I.useState)(`idle`),[a,o]=(0,I.useState)(null),s=JSON.stringify(e),c=(0,I.useRef)(s),l=(0,I.useRef)({value:e,json:s,save:t});l.current={value:e,json:s,save:t};let u=(0,I.useRef)(Promise.resolve()),d=(0,I.useCallback)(()=>{let e=l.current;e.json!==c.current&&(c.current=e.json,i(`saving`),u.current=u.current.then(async()=>{try{await e.save(e.value),o(null),i(`saved`)}catch(e){c.current=``,o(e.message),i(`error`)}}))},[]);return(0,I.useEffect)(()=>{if(s===c.current)return;let e=setTimeout(d,n);return()=>clearTimeout(e)},[s,n,d]),(0,I.useEffect)(()=>d,[d]),{state:r,error:a}}function U(e){return e<1e6?`${Math.max(1,Math.round(e/1e3))} KB`:`${(e/1e6).toFixed(1)} MB`}var W=e(n(),1);function G({id:e,children:t}){return(0,W.jsx)(`div`,{id:e,className:`scroll-mt-24 max-md:scroll-mt-36`,children:t})}function K({title:e,hint:t,save:n,action:r,locked:i,plain:a,children:o}){return(0,W.jsxs)(`section`,{className:`flex flex-col gap-2.5`,children:[(0,W.jsxs)(`div`,{className:`flex items-start justify-between gap-4 px-1`,children:[(0,W.jsxs)(`div`,{className:`min-w-0`,children:[(0,W.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,W.jsx)(`h2`,{className:`font-display font-semibold text-lg leading-tight`,children:e}),n?.state===`saving`?(0,W.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 text-[0.75rem] text-ink-3`,children:[(0,W.jsx)(m,{className:`size-3`}),` `,f(`project.saving`)]}):n?.state===`saved`?(0,W.jsxs)(`span`,{className:`inline-flex items-center gap-1 text-[0.75rem] text-moss`,children:[(0,W.jsx)(c,{className:`size-3.5`}),` `,f(`settings.saved`)]}):null]}),t?(0,W.jsx)(`p`,{className:`mt-1 text-[0.875rem] text-ink-3 leading-snug`,children:t}):null]}),r?(0,W.jsx)(`div`,{className:`shrink-0`,children:r}):null]}),a?o:(0,W.jsxs)(_,{className:`p-3.5 sm:p-5`,children:[i?(0,W.jsx)(`fieldset`,{disabled:!0,className:`min-w-0`,children:o}):o,n?.state===`error`&&n.error?(0,W.jsx)(`p`,{className:`mt-4 text-[0.875rem] text-rose`,children:n.error}):null]})]})}function q({children:e,layout:t,onToggle:n}){return(0,W.jsxs)(`div`,{className:`mb-3 flex items-start gap-2`,children:[(0,W.jsx)(`div`,{className:`min-w-0 flex-1`,children:(0,W.jsx)(`div`,{className:`w-56 max-w-full transition-[width] duration-200 focus-within:w-full has-[input:not(:placeholder-shown)]:w-full`,children:e})}),(0,W.jsx)(v,{label:f(t===`list`?`project.viewCards`:`project.viewList`),onClick:n,children:t===`list`?(0,W.jsx)(N,{className:`size-4`}):(0,W.jsx)(F,{className:`size-4`})})]})}function J({value:e,onChange:t,onEnter:n,placeholder:r,busy:i}){return(0,W.jsxs)(`div`,{className:`relative`,children:[(0,W.jsx)(l,{className:`-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 size-4 text-ink-4`}),(0,W.jsx)(`input`,{type:`search`,value:e,placeholder:r,onChange:e=>t(e.target.value),onKeyDown:e=>e.key===`Enter`&&n?.(),className:`h-9 w-full rounded-lg border border-input bg-card pr-3 pl-9 text-[0.875rem] text-ink outline-none transition placeholder:text-ink-4 focus:border-focus focus:ring-4 focus:ring-focus-glow`}),i?(0,W.jsx)(m,{className:`-translate-y-1/2 absolute top-1/2 right-3 size-4 text-ink-3`}):null]})}function Y({onFiles:e,accept:t,multiple:n=!0,className:r,children:i}){return(0,W.jsxs)(`label`,{onDragOver:e=>{e.preventDefault(),e.currentTarget.dataset.over=`1`},onDragLeave:e=>{delete e.currentTarget.dataset.over},onDrop:t=>{t.preventDefault(),delete t.currentTarget.dataset.over;let r=Array.from(t.dataTransfer.files);r.length&&e(n?r:r.slice(0,1))},className:h(`flex cursor-pointer items-center justify-center gap-2 rounded-lg border border-input border-dashed text-[0.875rem] text-ink-3 transition focus-within:border-focus focus-within:ring-4 focus-within:ring-focus-glow hover:border-ink-4 hover:text-ink data-[over]:border-focus data-[over]:bg-paper-2 data-[over]:text-ink`,r),children:[i,(0,W.jsx)(`input`,{className:`sr-only`,type:`file`,accept:t,multiple:n,onChange:t=>{let n=Array.from(t.target.files??[]);t.target.value=``,n.length&&e(n)}})]})}var X=`mt-1.5 text-[0.8125rem] text-ink-3`;function Z(e){let t=o(),[n,r]=(0,I.useState)(!1),i=C({mutationFn:e=>a.post(`/api/studio/account/link`,e),onSuccess:({url:e})=>{k(e),r(!0)}});return(0,I.useEffect)(()=>{if(!n)return;let e=setInterval(()=>void t.invalidateQueries({queryKey:[`me`]}),2e3),i=setTimeout(()=>r(!1),3e5);return()=>{clearInterval(e),clearTimeout(i)}},[n,t]),(0,I.useEffect)(()=>{e&&r(!1)},[e]),{link:i,waiting:n}}function Q({link:e,waiting:t}){return e.isError?(0,W.jsx)(`p`,{className:`text-[0.8125rem] text-rose`,children:f(`local.accountFailed`)}):t?(0,W.jsxs)(`p`,{className:`flex items-center gap-2 text-[0.8125rem] text-ink-3`,children:[(0,W.jsx)(m,{className:`size-3.5`}),` `,f(`local.accountWaiting`)]}):null}function ne(){let e=Z(!1),{link:t}=e,[n,r]=(0,I.useState)(``);return(0,W.jsxs)(`div`,{className:`flex flex-col divide-y divide-border-soft`,children:[(0,W.jsxs)(`div`,{className:`flex items-start gap-4 py-5 first:pt-0 last:pb-0`,children:[(0,W.jsx)(`div`,{className:`flex size-11 shrink-0 items-center justify-center rounded-full bg-paper-2 text-ink-2`,children:(0,W.jsx)(j,{className:`size-5`})}),(0,W.jsxs)(`div`,{className:`min-w-0`,children:[(0,W.jsx)(`div`,{className:`font-medium text-[0.9375rem]`,children:f(`account.local`)}),(0,W.jsx)(`p`,{className:`mt-1 text-[0.875rem] text-ink-3 leading-relaxed`,children:f(`account.localText`)})]})]}),E.account?(0,W.jsxs)(`div`,{className:`flex flex-col gap-4 py-5 last:pb-0`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`font-medium text-[0.9375rem]`,children:f(`account.with`)}),(0,W.jsxs)(`ul`,{className:`mt-2 flex flex-col gap-2 text-[0.875rem] text-ink-2 leading-snug`,children:[(0,W.jsxs)(`li`,{className:`flex items-start gap-2.5`,children:[(0,W.jsx)(u,{className:`mt-0.5 size-4 shrink-0 text-amber`}),f(`account.withCredits`)]}),(0,W.jsxs)(`li`,{className:`flex items-start gap-2.5`,children:[(0,W.jsx)(w,{className:`mt-0.5 size-4 shrink-0 text-cobalt`}),f(`account.withCloud`)]})]})]}),(0,W.jsx)(`div`,{children:(0,W.jsx)(x,{busy:t.isPending&&!t.variables?.signup,onClick:()=>t.mutate({}),children:f(`account.signIn`)})})]}):null,E.account?(0,W.jsxs)(`form`,{className:`flex flex-col gap-3 py-5 last:pb-0`,onSubmit:e=>{e.preventDefault(),t.mutate({signup:!0,code:n.trim()||void 0})},children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(`div`,{className:`font-medium text-[0.9375rem]`,children:f(`account.new`)}),(0,W.jsx)(`p`,{className:`mt-1 text-[0.875rem] text-ink-3 leading-relaxed`,children:f(`account.newHint`)})]}),(0,W.jsxs)(`div`,{className:`flex flex-wrap gap-2`,children:[(0,W.jsx)(b,{value:n,maxLength:64,autoComplete:`off`,spellCheck:!1,placeholder:f(`account.code`),"aria-label":f(`account.code`),onChange:e=>r(e.target.value),className:`w-56 font-mono text-[0.875rem]`}),(0,W.jsx)(x,{type:`submit`,variant:`secondary`,className:`h-11`,busy:t.isPending&&!!t.variables?.signup,children:f(`account.create`)})]}),(0,W.jsx)(Q,{...e})]}):null]})}var $={start:`account.kind.start`,monthly:`account.kind.monthly`,gift:`account.kind.gift`};function re({account:e}){let t=o(),n=Z(e.signedIn),r=C({mutationFn:()=>a.del(`/api/studio/account`),onSuccess:()=>t.invalidateQueries({queryKey:[`me`]})}),i=new Intl.DateTimeFormat(S,{dateStyle:`medium`});return(0,W.jsxs)(`div`,{className:`flex flex-col divide-y divide-border-soft`,children:[(0,W.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-4 gap-y-3 py-5 first:pt-0`,children:[(0,W.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,W.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,W.jsx)(`span`,{className:`truncate font-medium text-[0.9375rem]`,children:e.name||e.email}),e.signedIn?(0,W.jsx)(y,{tone:`live`,children:f(`account.linked`)}):null]}),(0,W.jsx)(`div`,{className:`truncate text-[0.875rem] text-ink-3`,children:e.email})]}),(0,W.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,W.jsxs)(x,{variant:`secondary`,onClick:()=>k(e.url),children:[f(`setup.openAccount`),` `,(0,W.jsx)(d,{className:`size-4`})]}),(0,W.jsxs)(x,{variant:`ghost`,busy:r.isPending,onClick:()=>r.mutate(),children:[(0,W.jsx)(ee,{className:`size-4`}),` `,f(`nav.logout`)]})]})]}),e.signedIn?null:(0,W.jsxs)(`div`,{className:`flex flex-col gap-3 py-5`,children:[(0,W.jsx)(`p`,{className:`text-[0.875rem] text-ink-2`,children:f(`local.accountExpired`)}),(0,W.jsx)(`div`,{children:(0,W.jsx)(x,{busy:n.link.isPending,onClick:()=>n.link.mutate({}),children:f(`account.signInAgain`)})}),(0,W.jsx)(Q,{...n})]}),e.credits===null?null:(0,W.jsxs)(`div`,{className:`py-5`,children:[(0,W.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-4 gap-y-3`,children:[(0,W.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,W.jsx)(`div`,{className:`text-[0.8125rem] text-ink-3`,children:f(`account.credits`)}),(0,W.jsx)(`div`,{className:`font-display font-semibold text-[1.375rem] tabular-nums leading-tight`,children:f(`nav.credits`,{n:Math.floor(e.credits).toLocaleString()})})]}),(0,W.jsxs)(x,{variant:`secondary`,onClick:()=>k(`${e.url}/billing`),children:[f(`account.topUp`),` `,(0,W.jsx)(d,{className:`size-4`})]})]}),e.expiring.length?(0,W.jsx)(`ul`,{className:`mt-3 flex flex-col gap-1 text-[0.8125rem] text-ink-3 tabular-nums`,children:e.expiring.map(e=>(0,W.jsxs)(`li`,{children:[f(`account.expires`,{n:Math.floor(e.credits).toLocaleString(),date:i.format(new Date(e.expiresAt))}),$[e.kind]?(0,W.jsxs)(`span`,{className:`text-ink-4`,children:[` · `,f($[e.kind])]}):null]},`${e.kind}:${e.expiresAt}`))}):null]}),(0,W.jsxs)(`div`,{className:`flex items-start gap-2.5 py-5 text-[0.875rem] text-ink-2 leading-snug last:pb-0`,children:[(0,W.jsx)(w,{className:`mt-0.5 size-4 shrink-0 text-cobalt`}),(0,W.jsx)(`p`,{children:f(`account.cloud`,{host:te(e.cloudUrl)})})]})]})}function ie({me:e,fromAccount:t}){let n=o(),[r,i]=(0,I.useState)(e.profile),s=e=>i(t=>({...t,...e})),c=H(r,async e=>{await a.put(`/api/studio/profile`,e),await n.invalidateQueries({queryKey:[`me`]})});return(0,W.jsx)(K,{title:f(`account.profile`),hint:f(`account.hint`),save:c,children:(0,W.jsxs)(`div`,{className:`flex flex-col gap-6 sm:flex-row sm:gap-8`,children:[(0,W.jsxs)(`div`,{className:`flex shrink-0 flex-col items-center gap-2 sm:w-32`,children:[(0,W.jsx)(`div`,{className:`flex size-20 items-center justify-center overflow-hidden rounded-full bg-ember-tint font-semibold text-[1.75rem] text-ember-strong ring-1 ring-border-soft`,children:e.user.image?(0,W.jsx)(`img`,{src:e.user.image,alt:``,className:`size-full object-cover`}):O(r.name)||`?`}),(0,W.jsx)(`p`,{className:`text-center text-[0.75rem] text-ink-3 leading-snug`,children:f(`account.initials`)})]}),(0,W.jsxs)(`div`,{className:`flex min-w-0 flex-1 flex-col gap-5`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(p,{children:f(`account.name`)}),(0,W.jsx)(b,{value:r.name,maxLength:80,disabled:t,onChange:e=>s({name:e.target.value})}),(0,W.jsx)(`p`,{className:X,children:f(t?`account.fromAccount`:`account.nameHint`)})]}),(0,W.jsxs)(`div`,{children:[(0,W.jsx)(p,{children:f(`account.about`)}),(0,W.jsx)(g,{minRows:3,maxLength:2e3,value:r.about,onChange:e=>s({about:e.target.value})}),(0,W.jsx)(`p`,{className:X,children:f(`account.aboutHint`)})]}),(0,W.jsxs)(`div`,{className:`grid gap-5 sm:grid-cols-2`,children:[(0,W.jsxs)(`div`,{children:[(0,W.jsx)(p,{children:f(`account.email`)}),(0,W.jsx)(b,{type:`email`,value:r.email,maxLength:200,disabled:t,onChange:e=>s({email:e.target.value})})]}),(0,W.jsxs)(`div`,{children:[(0,W.jsx)(p,{children:f(`account.phone`)}),(0,W.jsx)(b,{type:`tel`,value:r.phone,maxLength:40,onChange:e=>s({phone:e.target.value})})]})]})]})]})})}function ae({me:e}){let t=e.mode===`managed`,n=t||!!e.account;return(0,W.jsxs)(`div`,{className:`flex flex-col gap-9`,children:[(0,W.jsx)(K,{title:f(`account.connection`),hint:f(`account.connectionHint`),children:t?(0,W.jsxs)(`div`,{className:`flex flex-wrap items-center gap-4`,children:[(0,W.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,W.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,W.jsx)(`span`,{className:`truncate font-medium text-[0.9375rem]`,children:e.user.name}),(0,W.jsx)(y,{tone:`live`,children:f(`account.linked`)})]}),(0,W.jsx)(`div`,{className:`truncate text-[0.875rem] text-ink-3`,children:e.user.email})]}),e.manageUrl?(0,W.jsxs)(x,{variant:`secondary`,onClick:()=>k(`${e.manageUrl}/account`),children:[f(`setup.openAccount`),` `,(0,W.jsx)(d,{className:`size-4`})]}):null]}):e.account?(0,W.jsx)(re,{account:e.account}):(0,W.jsx)(ne,{})}),(0,W.jsx)(ie,{me:e,fromAccount:n},n?`account`:`own`),(0,W.jsx)(K,{title:f(`account.look`),hint:f(`account.lookHint`),children:(0,W.jsxs)(`div`,{className:`flex flex-col divide-y divide-border-soft`,children:[(0,W.jsxs)(`div`,{className:`flex items-center justify-between gap-4 pb-4`,children:[(0,W.jsx)(`span`,{className:`font-medium text-[0.875rem]`,children:f(`nav.language`)}),(0,W.jsx)(T,{tone:`surface`})]}),(0,W.jsxs)(`div`,{className:`flex items-center justify-between gap-4 pt-4`,children:[(0,W.jsx)(`span`,{className:`font-medium text-[0.875rem]`,children:f(`nav.theme`)}),(0,W.jsx)(D,{})]})]})})]})}export{Y as a,K as c,H as d,B as f,N as h,G as i,U as l,R as m,Q as n,q as o,V as p,Z as r,J as s,ae as t,z as u};
@@ -1 +1 @@
1
- import{a as e,n as t,t as n}from"./jsx-runtime-D3jfb0Ew.js";import{t as r}from"./preload-helper-CNEI7N0N.js";import{a as i,b as a,d as o,h as s,i as c,s as l}from"./ui-BPY2pIh0.js";var u=e(t(),1),d=e(n(),1);function f(e){switch(e.group){case`Added`:return{label:`NEW`,dot:`bg-moss`};case`Fixed`:return{label:`FIXED`,dot:`bg-amber`};case`Changed`:return{label:`CHANGED`,dot:`bg-cobalt`};case`Performance`:return{label:`PERFORMANCE`,dot:`bg-ember`};case`Deploy`:return{label:`DEPLOY`,dot:`bg-ink-4`};case`Docs`:return{label:`DOCS`,dot:`bg-ink-4`};default:return{label:`OTHER`,dot:`bg-ink-4`}}}var p=e=>e.version?e.version.replace(/^v/,``):a(`about.unreleased`),m=e=>e.timestamp?new Date(e.timestamp*1e3).toISOString().slice(0,10):``,h=e=>e.charAt(0).toUpperCase()+e.slice(1);function g(e,t){let n=t.trim().toLowerCase();if(!n)return e;let r=[];for(let t of e){if(`${p(t)} ${m(t)}`.toLowerCase().includes(n)){r.push(t);continue}let e=t.commits.filter(e=>`${f(e).label} ${e.scope??``} ${e.message}`.toLowerCase().includes(n));e.length>0&&r.push({...t,commits:e})}return r}async function _(e){if(e)try{let t=await fetch(`https://raw.githubusercontent.com/engenty/engenty-wizards/v${e}/apps/web/src/about/changelog.json`,{signal:AbortSignal.timeout(5e3)});if(t.ok)return await t.json()}catch{}return(await r(async()=>{let{default:e}=await import(`./changelog-CXXT0zsp.js`);return{default:e}},[])).default}function v({open:e,onClose:t,version:n}){let[r,v]=(0,u.useState)(null),[y,b]=(0,u.useState)(``);(0,u.useEffect)(()=>{if(!e||r)return;let t=!1;return _(n).then(e=>{t||v(e)}),()=>{t=!0}},[e,r,n]),(0,u.useEffect)(()=>{e||b(``)},[e]);let x=r?g(r,y):[];return(0,d.jsxs)(c,{open:e,onClose:t,title:a(`about.changelog`),wide:!0,children:[(0,d.jsx)(`p`,{className:`-mt-3 mb-4 text-[0.875rem] text-ink-3`,children:a(`about.changelogHint`)}),r===null?(0,d.jsx)(`div`,{className:`flex justify-center py-10 text-ink-3`,children:(0,d.jsx)(o,{})}):r.length===0?(0,d.jsx)(i,{children:a(`about.changelogEmpty`)}):(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(l,{type:`search`,value:y,placeholder:a(`about.search`),onChange:e=>b(e.target.value)}),x.length===0?(0,d.jsx)(i,{children:a(`about.noMatches`)}):null,x.map(e=>(0,d.jsxs)(`section`,{className:`mt-6`,children:[(0,d.jsxs)(`h3`,{className:`flex items-baseline gap-3 border-border-soft border-b pb-2 font-display font-semibold text-[1rem]`,children:[p(e),(0,d.jsx)(`span`,{className:`font-normal font-sans text-[0.8125rem] text-ink-3 tabular-nums`,children:m(e)})]}),(0,d.jsx)(`ul`,{className:`mt-3 flex flex-col gap-2`,children:e.commits.map(e=>{let t=f(e);return(0,d.jsxs)(`li`,{className:`flex items-start gap-2.5 text-[0.875rem]`,children:[(0,d.jsx)(`span`,{className:s(`mt-[7px] size-1.5 shrink-0 rounded-full`,t.dot)}),(0,d.jsxs)(`span`,{className:`min-w-0 text-ink-2`,children:[(0,d.jsxs)(`span`,{className:`font-semibold text-[0.75rem] text-ink tracking-wide`,children:[t.label,e.scope?` [${e.scope}]`:``]}),` `,h(e.message),e.breaking?(0,d.jsx)(`span`,{className:`ml-1.5 rounded-full bg-rose-tint px-1.5 py-0.5 font-medium text-[0.75rem] text-rose`,children:a(`about.breaking`)}):null]})]},e.id)})})]},e.version??`unreleased`))]})]})}export{v as ChangelogDialog};
1
+ import{a as e,n as t,t as n}from"./jsx-runtime-D3jfb0Ew.js";import{t as r}from"./preload-helper-CNEI7N0N.js";import{a as i,b as a,d as o,h as s,i as c,s as l}from"./ui-CwU0VR_B.js";var u=e(t(),1),d=e(n(),1);function f(e){switch(e.group){case`Added`:return{label:`NEW`,dot:`bg-moss`};case`Fixed`:return{label:`FIXED`,dot:`bg-amber`};case`Changed`:return{label:`CHANGED`,dot:`bg-cobalt`};case`Performance`:return{label:`PERFORMANCE`,dot:`bg-ember`};case`Deploy`:return{label:`DEPLOY`,dot:`bg-ink-4`};case`Docs`:return{label:`DOCS`,dot:`bg-ink-4`};default:return{label:`OTHER`,dot:`bg-ink-4`}}}var p=e=>e.version?e.version.replace(/^v/,``):a(`about.unreleased`),m=e=>e.timestamp?new Date(e.timestamp*1e3).toISOString().slice(0,10):``,h=e=>e.charAt(0).toUpperCase()+e.slice(1);function g(e,t){let n=t.trim().toLowerCase();if(!n)return e;let r=[];for(let t of e){if(`${p(t)} ${m(t)}`.toLowerCase().includes(n)){r.push(t);continue}let e=t.commits.filter(e=>`${f(e).label} ${e.scope??``} ${e.message}`.toLowerCase().includes(n));e.length>0&&r.push({...t,commits:e})}return r}async function _(e){if(e)try{let t=await fetch(`https://raw.githubusercontent.com/engenty/engenty-wizards/v${e}/apps/web/src/about/changelog.json`,{signal:AbortSignal.timeout(5e3)});if(t.ok)return await t.json()}catch{}return(await r(async()=>{let{default:e}=await import(`./changelog-DMAoeotb.js`);return{default:e}},[])).default}function v({open:e,onClose:t,version:n}){let[r,v]=(0,u.useState)(null),[y,b]=(0,u.useState)(``);(0,u.useEffect)(()=>{if(!e||r)return;let t=!1;return _(n).then(e=>{t||v(e)}),()=>{t=!0}},[e,r,n]),(0,u.useEffect)(()=>{e||b(``)},[e]);let x=r?g(r,y):[];return(0,d.jsxs)(c,{open:e,onClose:t,title:a(`about.changelog`),wide:!0,children:[(0,d.jsx)(`p`,{className:`-mt-3 mb-4 text-[0.875rem] text-ink-3`,children:a(`about.changelogHint`)}),r===null?(0,d.jsx)(`div`,{className:`flex justify-center py-10 text-ink-3`,children:(0,d.jsx)(o,{})}):r.length===0?(0,d.jsx)(i,{children:a(`about.changelogEmpty`)}):(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(l,{type:`search`,value:y,placeholder:a(`about.search`),onChange:e=>b(e.target.value)}),x.length===0?(0,d.jsx)(i,{children:a(`about.noMatches`)}):null,x.map(e=>(0,d.jsxs)(`section`,{className:`mt-6`,children:[(0,d.jsxs)(`h3`,{className:`flex items-baseline gap-3 border-border-soft border-b pb-2 font-display font-semibold text-[1rem]`,children:[p(e),(0,d.jsx)(`span`,{className:`font-normal font-sans text-[0.8125rem] text-ink-3 tabular-nums`,children:m(e)})]}),(0,d.jsx)(`ul`,{className:`mt-3 flex flex-col gap-2`,children:e.commits.map(e=>{let t=f(e);return(0,d.jsxs)(`li`,{className:`flex items-start gap-2.5 text-[0.875rem]`,children:[(0,d.jsx)(`span`,{className:s(`mt-[7px] size-1.5 shrink-0 rounded-full`,t.dot)}),(0,d.jsxs)(`span`,{className:`min-w-0 text-ink-2`,children:[(0,d.jsxs)(`span`,{className:`font-semibold text-[0.75rem] text-ink tracking-wide`,children:[t.label,e.scope?` [${e.scope}]`:``]}),` `,h(e.message),e.breaking?(0,d.jsx)(`span`,{className:`ml-1.5 rounded-full bg-rose-tint px-1.5 py-0.5 font-medium text-[0.75rem] text-rose`,children:a(`about.breaking`)}):null]})]},e.id)})})]},e.version??`unreleased`))]})]})}export{v as ChangelogDialog};