@burdenoff/microfe-vibecontrols 2026.520.2 → 2026.520.3

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.
@@ -50,32 +50,32 @@ var w = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/, T = {
50
50
  }
51
51
  ];
52
52
  function D() {
53
- let D = e(), A = v(), [j, M] = _(0), [N, P] = _(T), [F, I] = _(null), [L, R] = _(!1), { setDraftTagIds: z, flushTags: B } = m("VIBECONTROLS_TUNNEL"), { data: V, loading: H, error: U } = r(), { data: W } = n({ variables: { pagination: { limit: 100 } } }), [G, { loading: K }] = t(), q = h((e, t) => {
53
+ let D = e(), A = v(), [j, M] = _(0), [N, P] = _(T), [F, I] = _(null), [L, R] = _(!1), [z, B] = _(!1), { setDraftTagIds: V, flushTags: H } = m("VIBECONTROLS_TUNNEL"), { data: U, loading: W, error: G } = r(), { data: K } = n({ variables: { pagination: { limit: 100 } } }), [q, { loading: J }] = t(), Y = h((e, t) => {
54
54
  P((n) => ({
55
55
  ...n,
56
56
  [e]: t
57
57
  }));
58
- }, []), J = h((e) => {
58
+ }, []), X = h((e) => {
59
59
  P((t) => ({
60
60
  ...t,
61
61
  providerKey: e,
62
62
  hostnameKind: e === u.Cloudflare ? "auto" : "managed"
63
63
  }));
64
- }, []), Y = g(() => [...V?.tunnelProviders ?? []].sort((e, t) => {
64
+ }, []), Z = g(() => [...U?.tunnelProviders ?? []].sort((e, t) => {
65
65
  let n = (e) => e === "VIBETUNNELS" ? 0 : e === "CLOUDFLARE" ? 1 : 2, r = n(e.key) - n(t.key);
66
66
  return r === 0 ? e.key.localeCompare(t.key) : r;
67
- }), [V]), X = g(() => Y.find((e) => e.key === N.providerKey), [Y, N.providerKey])?.capabilities, Z = N.providerKey === u.Cloudflare, Q = g(() => {
68
- if (!X) return [l.Http];
67
+ }), [U]), Q = g(() => Z.find((e) => e.key === N.providerKey), [Z, N.providerKey])?.capabilities, $ = N.providerKey === u.Cloudflare, ee = g(() => {
68
+ if (!Q) return [l.Http];
69
69
  let e = [];
70
- return X.supportsHttp && e.push(l.Http), X.supportsHttps && e.push(l.Https), X.supportsTcp && e.push(l.Tcp), e;
71
- }, [X]), $ = g(() => j === 0 ? !!(N.providerKey && N.agentId && N.name.trim()) : j === 1 ? N.localPort > 0 && N.localPort < 65536 : j === 2 ? N.hostnameKind === "managed" ? !!(N.subdomain && w.test(N.subdomain)) : N.hostnameKind === "custom" ? N.customDomain.trim().includes(".") : Z : !0, [
70
+ return Q.supportsHttp && e.push(l.Http), Q.supportsHttps && e.push(l.Https), Q.supportsTcp && e.push(l.Tcp), e;
71
+ }, [Q]), te = g(() => j === 0 ? !!(N.providerKey && N.agentId && N.name.trim()) : j === 1 ? N.localPort > 0 && N.localPort < 65536 : j === 2 ? N.hostnameKind === "managed" ? !!(N.subdomain && w.test(N.subdomain)) : N.hostnameKind === "custom" ? N.customDomain.trim().includes(".") : $ : !0, [
72
72
  j,
73
73
  N,
74
- Z
75
- ]), ee = () => M((e) => Math.min(e + 1, E.length - 1)), te = () => M((e) => Math.max(e - 1, 0)), ne = async () => {
74
+ $
75
+ ]), ne = () => M((e) => Math.min(e + 1, E.length - 1)), re = () => M((e) => Math.max(e - 1, 0)), ie = async () => {
76
76
  I(null);
77
77
  try {
78
- let e = await G({ variables: { input: {
78
+ let e = await q({ variables: { input: {
79
79
  agentId: N.agentId,
80
80
  name: N.name,
81
81
  description: N.description || void 0,
@@ -90,25 +90,33 @@ function D() {
90
90
  } } });
91
91
  if (e.error) {
92
92
  if (f(e.error)) {
93
- R(!0);
93
+ B(!0);
94
94
  return;
95
95
  }
96
96
  I(e.error.message);
97
97
  return;
98
98
  }
99
99
  let t = e.data?.createTunnel.id;
100
- t && (await B(t), A(`/vibecontrols/tunnels/${t}`));
100
+ if (t) {
101
+ R(!0);
102
+ try {
103
+ await H(t);
104
+ } finally {
105
+ R(!1);
106
+ }
107
+ A(`/vibecontrols/tunnels/${t}`);
108
+ }
101
109
  } catch (e) {
102
110
  if (f(e)) {
103
- I(null), R(!0);
111
+ I(null), B(!0);
104
112
  return;
105
113
  }
106
114
  I(e instanceof Error ? e.message : String(e));
107
115
  }
108
116
  };
109
- return H ? /* @__PURE__ */ S(a, { message: "Loading providers" }) : U ? /* @__PURE__ */ S(o, {
117
+ return W ? /* @__PURE__ */ S(a, { message: "Loading providers" }) : G ? /* @__PURE__ */ S(o, {
110
118
  title: "Failed to load providers",
111
- message: U.message,
119
+ message: G.message,
112
120
  onRetry: () => window.location.reload()
113
121
  }) : /* @__PURE__ */ C("div", {
114
122
  className: "p-6 max-w-3xl mx-auto",
@@ -155,9 +163,9 @@ function D() {
155
163
  }),
156
164
  /* @__PURE__ */ S("div", {
157
165
  className: "grid gap-3 sm:grid-cols-2",
158
- children: Y.map((e) => /* @__PURE__ */ C("button", {
166
+ children: Z.map((e) => /* @__PURE__ */ C("button", {
159
167
  type: "button",
160
- onClick: () => J(e.key),
168
+ onClick: () => X(e.key),
161
169
  className: `rounded-lg border p-4 text-left transition-colors ${N.providerKey === e.key ? "border-action-primary-bg bg-bg-sunken" : "border-border-subtle hover:border-border-default"}`,
162
170
  children: [
163
171
  /* @__PURE__ */ S("div", {
@@ -179,12 +187,12 @@ function D() {
179
187
  label: "Agent",
180
188
  children: /* @__PURE__ */ C("select", {
181
189
  value: N.agentId,
182
- onChange: (e) => q("agentId", e.target.value),
190
+ onChange: (e) => Y("agentId", e.target.value),
183
191
  className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary",
184
192
  children: [/* @__PURE__ */ S("option", {
185
193
  value: "",
186
194
  children: "Select agent"
187
- }), (W?.vibecontrolsAgents ?? []).map((e) => /* @__PURE__ */ S("option", {
195
+ }), (K?.vibecontrolsAgents ?? []).map((e) => /* @__PURE__ */ S("option", {
188
196
  value: e.id,
189
197
  children: e.name
190
198
  }, e.id))]
@@ -195,7 +203,7 @@ function D() {
195
203
  children: /* @__PURE__ */ S("input", {
196
204
  type: "text",
197
205
  value: N.name,
198
- onChange: (e) => q("name", e.target.value),
206
+ onChange: (e) => Y("name", e.target.value),
199
207
  placeholder: "My dev tunnel",
200
208
  className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted"
201
209
  })
@@ -205,7 +213,7 @@ function D() {
205
213
  children: /* @__PURE__ */ S("input", {
206
214
  type: "text",
207
215
  value: N.description,
208
- onChange: (e) => q("description", e.target.value),
216
+ onChange: (e) => Y("description", e.target.value),
209
217
  className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted"
210
218
  })
211
219
  })
@@ -222,9 +230,9 @@ function D() {
222
230
  label: "Protocol",
223
231
  children: /* @__PURE__ */ S("div", {
224
232
  className: "flex gap-2",
225
- children: Q.map((e) => /* @__PURE__ */ S("button", {
233
+ children: ee.map((e) => /* @__PURE__ */ S("button", {
226
234
  type: "button",
227
- onClick: () => q("protocol", e),
235
+ onClick: () => Y("protocol", e),
228
236
  className: `rounded-md border px-4 py-2 text-sm ${N.protocol === e ? "border-action-primary-bg bg-bg-sunken text-text-primary" : "border-border-subtle text-text-secondary hover:border-border-default"}`,
229
237
  children: e
230
238
  }, e))
@@ -235,7 +243,7 @@ function D() {
235
243
  children: /* @__PURE__ */ S("input", {
236
244
  type: "text",
237
245
  value: N.localHost,
238
- onChange: (e) => q("localHost", e.target.value),
246
+ onChange: (e) => Y("localHost", e.target.value),
239
247
  className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary"
240
248
  })
241
249
  }),
@@ -244,7 +252,7 @@ function D() {
244
252
  children: /* @__PURE__ */ S("input", {
245
253
  type: "number",
246
254
  value: N.localPort,
247
- onChange: (e) => q("localPort", parseInt(e.target.value, 10) || 0),
255
+ onChange: (e) => Y("localPort", parseInt(e.target.value, 10) || 0),
248
256
  min: 1,
249
257
  max: 65535,
250
258
  className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary"
@@ -264,22 +272,22 @@ function D() {
264
272
  children: [
265
273
  /* @__PURE__ */ S(k, {
266
274
  active: N.hostnameKind === "managed",
267
- onClick: () => q("hostnameKind", "managed"),
268
- disabled: !X?.supportsManagedSubdomains,
275
+ onClick: () => Y("hostnameKind", "managed"),
276
+ disabled: !Q?.supportsManagedSubdomains,
269
277
  label: "Managed subdomain",
270
278
  hint: "*.vibetunnels.com"
271
279
  }),
272
280
  /* @__PURE__ */ S(k, {
273
281
  active: N.hostnameKind === "custom",
274
- onClick: () => q("hostnameKind", "custom"),
275
- disabled: !X?.supportsCustomDomains,
282
+ onClick: () => Y("hostnameKind", "custom"),
283
+ disabled: !Q?.supportsCustomDomains,
276
284
  label: "Custom domain",
277
285
  hint: "TXT verification"
278
286
  }),
279
287
  /* @__PURE__ */ S(k, {
280
288
  active: N.hostnameKind === "auto",
281
- onClick: () => q("hostnameKind", "auto"),
282
- disabled: !Z,
289
+ onClick: () => Y("hostnameKind", "auto"),
290
+ disabled: !$,
283
291
  label: "Auto",
284
292
  hint: "provider default"
285
293
  })
@@ -292,12 +300,12 @@ function D() {
292
300
  className: "flex gap-2",
293
301
  children: [/* @__PURE__ */ S("button", {
294
302
  type: "button",
295
- onClick: () => q("environment", c.Prod),
303
+ onClick: () => Y("environment", c.Prod),
296
304
  className: `rounded-md border px-4 py-2 text-sm ${N.environment === c.Prod ? "border-action-primary-bg bg-bg-sunken text-text-primary" : "border-border-subtle text-text-secondary hover:border-border-default"}`,
297
305
  children: "Prod (vibetunnels.com)"
298
306
  }), /* @__PURE__ */ S("button", {
299
307
  type: "button",
300
- onClick: () => q("environment", c.Alpha),
308
+ onClick: () => Y("environment", c.Alpha),
301
309
  className: `rounded-md border px-4 py-2 text-sm ${N.environment === c.Alpha ? "border-action-primary-bg bg-bg-sunken text-text-primary" : "border-border-subtle text-text-secondary hover:border-border-default"}`,
302
310
  children: "Alpha"
303
311
  })]
@@ -308,7 +316,7 @@ function D() {
308
316
  children: /* @__PURE__ */ S("input", {
309
317
  type: "text",
310
318
  value: N.subdomain,
311
- onChange: (e) => q("subdomain", e.target.value.toLowerCase()),
319
+ onChange: (e) => Y("subdomain", e.target.value.toLowerCase()),
312
320
  placeholder: "demo",
313
321
  className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary"
314
322
  })
@@ -330,7 +338,7 @@ function D() {
330
338
  children: [/* @__PURE__ */ S("input", {
331
339
  type: "text",
332
340
  value: N.customDomain,
333
- onChange: (e) => q("customDomain", e.target.value.toLowerCase()),
341
+ onChange: (e) => Y("customDomain", e.target.value.toLowerCase()),
334
342
  placeholder: "preview.example.com",
335
343
  className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary"
336
344
  }), /* @__PURE__ */ S("p", {
@@ -352,7 +360,7 @@ function D() {
352
360
  children: /* @__PURE__ */ S("input", {
353
361
  type: "text",
354
362
  value: N.basicAuthUser,
355
- onChange: (e) => q("basicAuthUser", e.target.value),
363
+ onChange: (e) => Y("basicAuthUser", e.target.value),
356
364
  className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary"
357
365
  })
358
366
  }),
@@ -361,7 +369,7 @@ function D() {
361
369
  children: /* @__PURE__ */ S("input", {
362
370
  type: "password",
363
371
  value: N.basicAuthPassword,
364
- onChange: (e) => q("basicAuthPassword", e.target.value),
372
+ onChange: (e) => Y("basicAuthPassword", e.target.value),
365
373
  className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary"
366
374
  })
367
375
  })
@@ -446,7 +454,7 @@ function D() {
446
454
  children: D("vibecontrols.tunnels.tags", "Tags")
447
455
  }), /* @__PURE__ */ S(p, {
448
456
  entityType: "VIBECONTROLS_TUNNEL",
449
- onDraftChange: z
457
+ onDraftChange: V
450
458
  })]
451
459
  }),
452
460
  F && /* @__PURE__ */ S("div", {
@@ -463,28 +471,28 @@ function D() {
463
471
  children: [/* @__PURE__ */ S("button", {
464
472
  type: "button",
465
473
  disabled: j === 0,
466
- onClick: te,
474
+ onClick: re,
467
475
  className: "rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-elevated disabled:opacity-50",
468
476
  children: D("vibecontrols.tunnels.back", "Back")
469
477
  }), j < E.length - 1 ? /* @__PURE__ */ S("button", {
470
478
  type: "button",
471
- disabled: !$,
472
- onClick: ee,
479
+ disabled: !te,
480
+ onClick: ne,
473
481
  className: "rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50",
474
482
  children: D("vibecontrols.tunnels.next", "Next")
475
483
  }) : /* @__PURE__ */ S("button", {
476
484
  type: "button",
477
- disabled: K,
478
- onClick: () => void ne(),
485
+ disabled: J || L,
486
+ onClick: () => void ie(),
479
487
  className: "rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50",
480
- children: K ? D("vibecontrols.tunnels.creating", "Creating...") : D("vibecontrols.tunnels.createTunnel", "Create tunnel")
488
+ children: J ? D("vibecontrols.tunnels.creating", "Creating...") : D("vibecontrols.tunnels.createTunnel", "Create tunnel")
481
489
  })]
482
490
  }),
483
491
  /* @__PURE__ */ S(s, {
484
- isOpen: L,
485
- onClose: () => R(!1),
492
+ isOpen: z,
493
+ onClose: () => B(!1),
486
494
  onConfirm: () => {
487
- R(!1), A("/billing");
495
+ B(!1), A("/billing");
488
496
  },
489
497
  ...d("tunnel-subdomain")
490
498
  })
@@ -1 +1 @@
1
- {"version":3,"file":"TunnelCreateWizard.js","names":[],"sources":["../../../src/components/tunnels/TunnelCreateWizard.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { useTr } from '../../shared/hooks/useTr';\n\nimport { PageHeader } from '@/components/shared/PageHeader';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { ErrorState } from '@/components/shared/ErrorState';\nimport { QuotaExhaustedDialog } from '@/components/shared/QuotaExhaustedDialog';\nimport {\n TunnelEnvironment,\n TunnelProtocol,\n TunnelProviderKey,\n type CreateTunnelInput,\n} from '@/generated/wspace-types';\nimport {\n useCreateTunnelMutation,\n useListTunnelProvidersQuery,\n useListAgentsQuery,\n} from '@/generated/wspace-operations';\nimport { getQuotaDialogContent, isQuotaExhaustedError } from '@/utils/quotaUtils';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { useDraftEntityTags } from '@/hooks/useDraftEntityTags';\n\nconst SUBDOMAIN_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;\n\ntype HostnameKind = 'managed' | 'custom' | 'auto';\n\ninterface WizardState {\n providerKey: TunnelProviderKey | '';\n agentId: string;\n name: string;\n description: string;\n protocol: TunnelProtocol;\n localHost: string;\n localPort: number;\n hostnameKind: HostnameKind;\n subdomain: string;\n environment: TunnelEnvironment;\n customDomain: string;\n basicAuthUser: string;\n basicAuthPassword: string;\n}\n\nconst INITIAL_STATE: WizardState = {\n // Default to Vibe Tunnels as the preferred provider; users can still pick\n // Cloudflare Tunnels from the provider list.\n providerKey: TunnelProviderKey.Vibetunnels,\n agentId: '',\n name: '',\n description: '',\n protocol: TunnelProtocol.Http,\n localHost: '127.0.0.1',\n localPort: 3000,\n hostnameKind: 'managed',\n subdomain: '',\n environment: TunnelEnvironment.Prod,\n customDomain: '',\n basicAuthUser: '',\n basicAuthPassword: '',\n};\n\nconst STEPS = [\n { id: 'provider', label: 'Provider' },\n { id: 'target', label: 'Protocol & target' },\n { id: 'hostname', label: 'Hostname' },\n { id: 'policies', label: 'Policies' },\n { id: 'review', label: 'Review' },\n] as const;\n\nexport function TunnelCreateWizard() {\n const tr = useTr();\n const navigate = useNavigate();\n const [step, setStep] = useState(0);\n const [state, setState] = useState<WizardState>(INITIAL_STATE);\n const [submitError, setSubmitError] = useState<string | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n // Tags selected before the tunnel exists — persisted right after create.\n const { setDraftTagIds, flushTags } = useDraftEntityTags('VIBECONTROLS_TUNNEL');\n\n const {\n data: providersData,\n loading: providersLoading,\n error: providersError,\n } = useListTunnelProvidersQuery();\n const { data: agentsData } = useListAgentsQuery({ variables: { pagination: { limit: 100 } } });\n\n const [createTunnel, { loading: creating }] = useCreateTunnelMutation();\n\n const update = useCallback(<K extends keyof WizardState>(key: K, value: WizardState[K]) => {\n setState((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const selectProvider = useCallback((providerKey: TunnelProviderKey) => {\n setState((prev) => ({\n ...prev,\n providerKey,\n hostnameKind: providerKey === TunnelProviderKey.Cloudflare ? 'auto' : 'managed',\n }));\n }, []);\n\n // VibeTunnels first, Cloudflare second, everything else alphabetically.\n const orderedProviders = useMemo(\n () =>\n [...(providersData?.tunnelProviders ?? [])].sort((a: { key: string }, b: { key: string }) => {\n const rank = (key: string) => (key === 'VIBETUNNELS' ? 0 : key === 'CLOUDFLARE' ? 1 : 2);\n const diff = rank(a.key) - rank(b.key);\n if (diff !== 0) return diff;\n return a.key.localeCompare(b.key);\n }),\n [providersData]\n );\n\n const selectedProvider = useMemo(\n () => orderedProviders.find((p: { key: string }) => p.key === state.providerKey),\n [orderedProviders, state.providerKey]\n );\n\n const caps = selectedProvider?.capabilities;\n const supportsAutoHostname = state.providerKey === TunnelProviderKey.Cloudflare;\n const protocolOptions = useMemo(() => {\n if (!caps) return [TunnelProtocol.Http];\n const list: TunnelProtocol[] = [];\n if (caps.supportsHttp) list.push(TunnelProtocol.Http);\n if (caps.supportsHttps) list.push(TunnelProtocol.Https);\n if (caps.supportsTcp) list.push(TunnelProtocol.Tcp);\n return list;\n }, [caps]);\n\n const canAdvance = useMemo(() => {\n if (step === 0) return Boolean(state.providerKey && state.agentId && state.name.trim());\n if (step === 1) return state.localPort > 0 && state.localPort < 65536;\n if (step === 2) {\n if (state.hostnameKind === 'managed')\n return Boolean(state.subdomain && SUBDOMAIN_RE.test(state.subdomain));\n if (state.hostnameKind === 'custom') return state.customDomain.trim().includes('.');\n return supportsAutoHostname;\n }\n return true;\n }, [step, state, supportsAutoHostname]);\n\n const next = () => setStep((s) => Math.min(s + 1, STEPS.length - 1));\n const back = () => setStep((s) => Math.max(s - 1, 0));\n\n const submit = async () => {\n setSubmitError(null);\n try {\n const input: CreateTunnelInput = {\n agentId: state.agentId,\n name: state.name,\n description: state.description || undefined,\n providerKey: state.providerKey as TunnelProviderKey,\n protocol: state.protocol,\n localHost: state.localHost,\n localPort: state.localPort,\n environment: state.environment,\n basicAuthUser: state.basicAuthUser || undefined,\n basicAuthPassword: state.basicAuthPassword || undefined,\n managedSubdomain: state.hostnameKind === 'managed' ? state.subdomain : undefined,\n };\n const result = await createTunnel({ variables: { input } });\n\n if (result.error) {\n if (isQuotaExhaustedError(result.error)) {\n setShowQuotaExhausted(true);\n return;\n }\n\n setSubmitError(result.error.message);\n return;\n }\n\n const id = result.data?.createTunnel.id;\n if (id) {\n await flushTags(id);\n navigate(`/vibecontrols/tunnels/${id}`);\n }\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setSubmitError(null);\n setShowQuotaExhausted(true);\n return;\n }\n\n setSubmitError(err instanceof Error ? err.message : String(err));\n }\n };\n\n if (providersLoading) return <LoadingState message=\"Loading providers\" />;\n if (providersError) {\n return (\n <ErrorState\n title=\"Failed to load providers\"\n message={providersError.message}\n onRetry={() => window.location.reload()}\n />\n );\n }\n\n return (\n <div className=\"p-6 max-w-3xl mx-auto\">\n <PageHeader\n breadcrumb={[{ label: 'Tunnels', path: '/vibecontrols/tunnels' }, { label: 'New tunnel' }]}\n title={tr('vibecontrols.tunnels.createTitle', 'Create tunnel')}\n description={tr(\n 'vibecontrols.tunnels.createDescription',\n 'Expose a local service through a managed tunnel.'\n )}\n />\n\n <div className=\"mb-6 flex items-center gap-2\">\n {STEPS.map((s, i) => (\n <div\n key={s.id}\n className={`flex-1 h-1.5 rounded-full ${\n i <= step ? 'bg-action-primary-bg' : 'bg-bg-sunken'\n }`}\n aria-label={`Step ${i + 1}: ${s.label}`}\n />\n ))}\n </div>\n\n <AnimatePresence mode=\"wait\">\n <motion.div\n key={step}\n initial={{ opacity: 0, x: 6 }}\n animate={{ opacity: 1, x: 0 }}\n exit={{ opacity: 0, x: -6 }}\n transition={{ duration: 0.15 }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-6\"\n >\n {step === 0 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Provider</h3>\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {orderedProviders.map(\n (p: {\n id: string;\n key: TunnelProviderKey;\n displayName: string;\n description?: string | null;\n }) => (\n <button\n key={p.id}\n type=\"button\"\n onClick={() => selectProvider(p.key)}\n className={`rounded-lg border p-4 text-left transition-colors ${\n state.providerKey === p.key\n ? 'border-action-primary-bg bg-bg-sunken'\n : 'border-border-subtle hover:border-border-default'\n }`}\n >\n <div className=\"font-semibold text-text-primary\">{p.displayName}</div>\n <div className=\"text-xs text-text-muted mt-1\">{p.key}</div>\n {p.description && (\n <div className=\"text-xs text-text-secondary mt-2\">{p.description}</div>\n )}\n </button>\n )\n )}\n </div>\n <LabeledField label=\"Agent\">\n <select\n value={state.agentId}\n onChange={(e) => update('agentId', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"\">Select agent</option>\n {(agentsData?.vibecontrolsAgents ?? []).map((a: { id: string; name: string }) => (\n <option key={a.id} value={a.id}>\n {a.name}\n </option>\n ))}\n </select>\n </LabeledField>\n <LabeledField label=\"Name\">\n <input\n type=\"text\"\n value={state.name}\n onChange={(e) => update('name', e.target.value)}\n placeholder=\"My dev tunnel\"\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted\"\n />\n </LabeledField>\n <LabeledField label=\"Description (optional)\">\n <input\n type=\"text\"\n value={state.description}\n onChange={(e) => update('description', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted\"\n />\n </LabeledField>\n </div>\n )}\n\n {step === 1 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Protocol & target</h3>\n <LabeledField label=\"Protocol\">\n <div className=\"flex gap-2\">\n {protocolOptions.map((proto) => (\n <button\n key={proto}\n type=\"button\"\n onClick={() => update('protocol', proto)}\n className={`rounded-md border px-4 py-2 text-sm ${\n state.protocol === proto\n ? 'border-action-primary-bg bg-bg-sunken text-text-primary'\n : 'border-border-subtle text-text-secondary hover:border-border-default'\n }`}\n >\n {proto}\n </button>\n ))}\n </div>\n </LabeledField>\n <LabeledField label=\"Local host\">\n <input\n type=\"text\"\n value={state.localHost}\n onChange={(e) => update('localHost', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n <LabeledField label=\"Local port\">\n <input\n type=\"number\"\n value={state.localPort}\n onChange={(e) => update('localPort', parseInt(e.target.value, 10) || 0)}\n min={1}\n max={65535}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n </div>\n )}\n\n {step === 2 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Hostname</h3>\n <div className=\"flex gap-2\">\n <HostnameKindButton\n active={state.hostnameKind === 'managed'}\n onClick={() => update('hostnameKind', 'managed')}\n disabled={!caps?.supportsManagedSubdomains}\n label=\"Managed subdomain\"\n hint=\"*.vibetunnels.com\"\n />\n <HostnameKindButton\n active={state.hostnameKind === 'custom'}\n onClick={() => update('hostnameKind', 'custom')}\n disabled={!caps?.supportsCustomDomains}\n label=\"Custom domain\"\n hint=\"TXT verification\"\n />\n <HostnameKindButton\n active={state.hostnameKind === 'auto'}\n onClick={() => update('hostnameKind', 'auto')}\n disabled={!supportsAutoHostname}\n label=\"Auto\"\n hint=\"provider default\"\n />\n </div>\n\n {state.hostnameKind === 'managed' && (\n <>\n <LabeledField label=\"Environment\">\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => update('environment', TunnelEnvironment.Prod)}\n className={`rounded-md border px-4 py-2 text-sm ${\n state.environment === TunnelEnvironment.Prod\n ? 'border-action-primary-bg bg-bg-sunken text-text-primary'\n : 'border-border-subtle text-text-secondary hover:border-border-default'\n }`}\n >\n Prod (vibetunnels.com)\n </button>\n <button\n type=\"button\"\n onClick={() => update('environment', TunnelEnvironment.Alpha)}\n className={`rounded-md border px-4 py-2 text-sm ${\n state.environment === TunnelEnvironment.Alpha\n ? 'border-action-primary-bg bg-bg-sunken text-text-primary'\n : 'border-border-subtle text-text-secondary hover:border-border-default'\n }`}\n >\n Alpha\n </button>\n </div>\n </LabeledField>\n <LabeledField label=\"Subdomain label\">\n <input\n type=\"text\"\n value={state.subdomain}\n onChange={(e) => update('subdomain', e.target.value.toLowerCase())}\n placeholder=\"demo\"\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n {state.subdomain && (\n <p className=\"text-xs text-text-secondary\">\n Will be reserved as{' '}\n <span className=\"font-mono text-text-primary\">\n {state.subdomain}\n {state.environment === TunnelEnvironment.Alpha\n ? '.alpha.vibetunnels.com'\n : '.vibetunnels.com'}\n </span>\n </p>\n )}\n </>\n )}\n\n {state.hostnameKind === 'custom' && (\n <LabeledField label=\"Custom domain\">\n <input\n type=\"text\"\n value={state.customDomain}\n onChange={(e) => update('customDomain', e.target.value.toLowerCase())}\n placeholder=\"preview.example.com\"\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <p className=\"text-xs text-text-muted mt-2\">\n Custom domains are registered on create but must be verified via a TXT record\n before they can be attached. You'll see the verification challenge in the\n Domains tab after creation.\n </p>\n </LabeledField>\n )}\n </div>\n )}\n\n {step === 3 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Policies (optional)</h3>\n <LabeledField label=\"Basic auth user\">\n <input\n type=\"text\"\n value={state.basicAuthUser}\n onChange={(e) => update('basicAuthUser', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n <LabeledField label=\"Basic auth password\">\n <input\n type=\"password\"\n value={state.basicAuthPassword}\n onChange={(e) => update('basicAuthPassword', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n </div>\n )}\n\n {step === 4 && (\n <div className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('vibecontrols.tunnels.review', 'Review')}\n </h3>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm\">\n <dt className=\"text-text-muted\">Provider</dt>\n <dd className=\"text-text-primary\">{state.providerKey}</dd>\n <dt className=\"text-text-muted\">Name</dt>\n <dd className=\"text-text-primary\">{state.name}</dd>\n <dt className=\"text-text-muted\">Agent</dt>\n <dd className=\"text-text-primary font-mono\">{state.agentId}</dd>\n <dt className=\"text-text-muted\">Protocol</dt>\n <dd className=\"text-text-primary\">{state.protocol}</dd>\n <dt className=\"text-text-muted\">Target</dt>\n <dd className=\"text-text-primary font-mono\">\n {state.localHost}:{state.localPort}\n </dd>\n <dt className=\"text-text-muted\">Hostname</dt>\n <dd className=\"text-text-primary\">\n {state.hostnameKind === 'managed'\n ? `${state.subdomain}.${state.environment === 'ALPHA' ? 'alpha.' : ''}vibetunnels.com`\n : state.hostnameKind === 'custom'\n ? state.customDomain\n : 'auto-assigned'}\n </dd>\n <dt className=\"text-text-muted\">Basic auth</dt>\n <dd className=\"text-text-primary\">{state.basicAuthUser ? 'yes' : 'no'}</dd>\n </dl>\n <div className=\"border-t border-border-subtle pt-3\">\n <h4 className=\"mb-2 text-sm font-medium text-text-secondary\">\n {tr('vibecontrols.tunnels.tags', 'Tags')}\n </h4>\n <EntityTagPicker entityType=\"VIBECONTROLS_TUNNEL\" onDraftChange={setDraftTagIds} />\n </div>\n {submitError && (\n <div className=\"rounded-md bg-status-error-bg p-3 text-sm text-status-error-fg\">\n {submitError}\n </div>\n )}\n </div>\n )}\n </motion.div>\n </AnimatePresence>\n\n <div className=\"flex items-center justify-between mt-4\">\n <button\n type=\"button\"\n disabled={step === 0}\n onClick={back}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-elevated disabled:opacity-50\"\n >\n {tr('vibecontrols.tunnels.back', 'Back')}\n </button>\n {step < STEPS.length - 1 ? (\n <button\n type=\"button\"\n disabled={!canAdvance}\n onClick={next}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {tr('vibecontrols.tunnels.next', 'Next')}\n </button>\n ) : (\n <button\n type=\"button\"\n disabled={creating}\n onClick={() => void submit()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {creating\n ? tr('vibecontrols.tunnels.creating', 'Creating...')\n : tr('vibecontrols.tunnels.createTunnel', 'Create tunnel')}\n </button>\n )}\n </div>\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing');\n }}\n {...getQuotaDialogContent('tunnel-subdomain')}\n />\n </div>\n );\n}\n\nfunction LabeledField({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <label className=\"block\">\n <span className=\"text-xs font-medium text-text-secondary\">{label}</span>\n <div className=\"mt-1\">{children}</div>\n </label>\n );\n}\n\nfunction HostnameKindButton({\n active,\n disabled,\n onClick,\n label,\n hint,\n}: {\n active: boolean;\n disabled?: boolean;\n onClick: () => void;\n label: string;\n hint: string;\n}) {\n return (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={onClick}\n className={`flex-1 rounded-md border p-3 text-left transition-colors ${\n active\n ? 'border-action-primary-bg bg-bg-sunken'\n : 'border-border-subtle hover:border-border-default'\n } ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}\n >\n <div className=\"text-sm font-medium text-text-primary\">{label}</div>\n <div className=\"text-xs text-text-muted mt-0.5\">{hint}</div>\n </button>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TunnelCreateWizard.js","names":[],"sources":["../../../src/components/tunnels/TunnelCreateWizard.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { useTr } from '../../shared/hooks/useTr';\n\nimport { PageHeader } from '@/components/shared/PageHeader';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { ErrorState } from '@/components/shared/ErrorState';\nimport { QuotaExhaustedDialog } from '@/components/shared/QuotaExhaustedDialog';\nimport {\n TunnelEnvironment,\n TunnelProtocol,\n TunnelProviderKey,\n type CreateTunnelInput,\n} from '@/generated/wspace-types';\nimport {\n useCreateTunnelMutation,\n useListTunnelProvidersQuery,\n useListAgentsQuery,\n} from '@/generated/wspace-operations';\nimport { getQuotaDialogContent, isQuotaExhaustedError } from '@/utils/quotaUtils';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { useDraftEntityTags } from '@/hooks/useDraftEntityTags';\n\nconst SUBDOMAIN_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;\n\ntype HostnameKind = 'managed' | 'custom' | 'auto';\n\ninterface WizardState {\n providerKey: TunnelProviderKey | '';\n agentId: string;\n name: string;\n description: string;\n protocol: TunnelProtocol;\n localHost: string;\n localPort: number;\n hostnameKind: HostnameKind;\n subdomain: string;\n environment: TunnelEnvironment;\n customDomain: string;\n basicAuthUser: string;\n basicAuthPassword: string;\n}\n\nconst INITIAL_STATE: WizardState = {\n // Default to Vibe Tunnels as the preferred provider; users can still pick\n // Cloudflare Tunnels from the provider list.\n providerKey: TunnelProviderKey.Vibetunnels,\n agentId: '',\n name: '',\n description: '',\n protocol: TunnelProtocol.Http,\n localHost: '127.0.0.1',\n localPort: 3000,\n hostnameKind: 'managed',\n subdomain: '',\n environment: TunnelEnvironment.Prod,\n customDomain: '',\n basicAuthUser: '',\n basicAuthPassword: '',\n};\n\nconst STEPS = [\n { id: 'provider', label: 'Provider' },\n { id: 'target', label: 'Protocol & target' },\n { id: 'hostname', label: 'Hostname' },\n { id: 'policies', label: 'Policies' },\n { id: 'review', label: 'Review' },\n] as const;\n\nexport function TunnelCreateWizard() {\n const tr = useTr();\n const navigate = useNavigate();\n const [step, setStep] = useState(0);\n const [state, setState] = useState<WizardState>(INITIAL_STATE);\n const [submitError, setSubmitError] = useState<string | null>(null);\n const [isFlushingTags, setIsFlushingTags] = useState(false);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n // Tags selected before the tunnel exists — persisted right after create.\n const { setDraftTagIds, flushTags } = useDraftEntityTags('VIBECONTROLS_TUNNEL');\n\n const {\n data: providersData,\n loading: providersLoading,\n error: providersError,\n } = useListTunnelProvidersQuery();\n const { data: agentsData } = useListAgentsQuery({ variables: { pagination: { limit: 100 } } });\n\n const [createTunnel, { loading: creating }] = useCreateTunnelMutation();\n\n const update = useCallback(<K extends keyof WizardState>(key: K, value: WizardState[K]) => {\n setState((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const selectProvider = useCallback((providerKey: TunnelProviderKey) => {\n setState((prev) => ({\n ...prev,\n providerKey,\n hostnameKind: providerKey === TunnelProviderKey.Cloudflare ? 'auto' : 'managed',\n }));\n }, []);\n\n // VibeTunnels first, Cloudflare second, everything else alphabetically.\n const orderedProviders = useMemo(\n () =>\n [...(providersData?.tunnelProviders ?? [])].sort((a: { key: string }, b: { key: string }) => {\n const rank = (key: string) => (key === 'VIBETUNNELS' ? 0 : key === 'CLOUDFLARE' ? 1 : 2);\n const diff = rank(a.key) - rank(b.key);\n if (diff !== 0) return diff;\n return a.key.localeCompare(b.key);\n }),\n [providersData]\n );\n\n const selectedProvider = useMemo(\n () => orderedProviders.find((p: { key: string }) => p.key === state.providerKey),\n [orderedProviders, state.providerKey]\n );\n\n const caps = selectedProvider?.capabilities;\n const supportsAutoHostname = state.providerKey === TunnelProviderKey.Cloudflare;\n const protocolOptions = useMemo(() => {\n if (!caps) return [TunnelProtocol.Http];\n const list: TunnelProtocol[] = [];\n if (caps.supportsHttp) list.push(TunnelProtocol.Http);\n if (caps.supportsHttps) list.push(TunnelProtocol.Https);\n if (caps.supportsTcp) list.push(TunnelProtocol.Tcp);\n return list;\n }, [caps]);\n\n const canAdvance = useMemo(() => {\n if (step === 0) return Boolean(state.providerKey && state.agentId && state.name.trim());\n if (step === 1) return state.localPort > 0 && state.localPort < 65536;\n if (step === 2) {\n if (state.hostnameKind === 'managed')\n return Boolean(state.subdomain && SUBDOMAIN_RE.test(state.subdomain));\n if (state.hostnameKind === 'custom') return state.customDomain.trim().includes('.');\n return supportsAutoHostname;\n }\n return true;\n }, [step, state, supportsAutoHostname]);\n\n const next = () => setStep((s) => Math.min(s + 1, STEPS.length - 1));\n const back = () => setStep((s) => Math.max(s - 1, 0));\n\n const submit = async () => {\n setSubmitError(null);\n try {\n const input: CreateTunnelInput = {\n agentId: state.agentId,\n name: state.name,\n description: state.description || undefined,\n providerKey: state.providerKey as TunnelProviderKey,\n protocol: state.protocol,\n localHost: state.localHost,\n localPort: state.localPort,\n environment: state.environment,\n basicAuthUser: state.basicAuthUser || undefined,\n basicAuthPassword: state.basicAuthPassword || undefined,\n managedSubdomain: state.hostnameKind === 'managed' ? state.subdomain : undefined,\n };\n const result = await createTunnel({ variables: { input } });\n\n if (result.error) {\n if (isQuotaExhaustedError(result.error)) {\n setShowQuotaExhausted(true);\n return;\n }\n\n setSubmitError(result.error.message);\n return;\n }\n\n const id = result.data?.createTunnel.id;\n if (id) {\n setIsFlushingTags(true);\n try {\n await flushTags(id);\n } finally {\n setIsFlushingTags(false);\n }\n navigate(`/vibecontrols/tunnels/${id}`);\n }\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setSubmitError(null);\n setShowQuotaExhausted(true);\n return;\n }\n\n setSubmitError(err instanceof Error ? err.message : String(err));\n }\n };\n\n if (providersLoading) return <LoadingState message=\"Loading providers\" />;\n if (providersError) {\n return (\n <ErrorState\n title=\"Failed to load providers\"\n message={providersError.message}\n onRetry={() => window.location.reload()}\n />\n );\n }\n\n return (\n <div className=\"p-6 max-w-3xl mx-auto\">\n <PageHeader\n breadcrumb={[{ label: 'Tunnels', path: '/vibecontrols/tunnels' }, { label: 'New tunnel' }]}\n title={tr('vibecontrols.tunnels.createTitle', 'Create tunnel')}\n description={tr(\n 'vibecontrols.tunnels.createDescription',\n 'Expose a local service through a managed tunnel.'\n )}\n />\n\n <div className=\"mb-6 flex items-center gap-2\">\n {STEPS.map((s, i) => (\n <div\n key={s.id}\n className={`flex-1 h-1.5 rounded-full ${\n i <= step ? 'bg-action-primary-bg' : 'bg-bg-sunken'\n }`}\n aria-label={`Step ${i + 1}: ${s.label}`}\n />\n ))}\n </div>\n\n <AnimatePresence mode=\"wait\">\n <motion.div\n key={step}\n initial={{ opacity: 0, x: 6 }}\n animate={{ opacity: 1, x: 0 }}\n exit={{ opacity: 0, x: -6 }}\n transition={{ duration: 0.15 }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-6\"\n >\n {step === 0 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Provider</h3>\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {orderedProviders.map(\n (p: {\n id: string;\n key: TunnelProviderKey;\n displayName: string;\n description?: string | null;\n }) => (\n <button\n key={p.id}\n type=\"button\"\n onClick={() => selectProvider(p.key)}\n className={`rounded-lg border p-4 text-left transition-colors ${\n state.providerKey === p.key\n ? 'border-action-primary-bg bg-bg-sunken'\n : 'border-border-subtle hover:border-border-default'\n }`}\n >\n <div className=\"font-semibold text-text-primary\">{p.displayName}</div>\n <div className=\"text-xs text-text-muted mt-1\">{p.key}</div>\n {p.description && (\n <div className=\"text-xs text-text-secondary mt-2\">{p.description}</div>\n )}\n </button>\n )\n )}\n </div>\n <LabeledField label=\"Agent\">\n <select\n value={state.agentId}\n onChange={(e) => update('agentId', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"\">Select agent</option>\n {(agentsData?.vibecontrolsAgents ?? []).map((a: { id: string; name: string }) => (\n <option key={a.id} value={a.id}>\n {a.name}\n </option>\n ))}\n </select>\n </LabeledField>\n <LabeledField label=\"Name\">\n <input\n type=\"text\"\n value={state.name}\n onChange={(e) => update('name', e.target.value)}\n placeholder=\"My dev tunnel\"\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted\"\n />\n </LabeledField>\n <LabeledField label=\"Description (optional)\">\n <input\n type=\"text\"\n value={state.description}\n onChange={(e) => update('description', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted\"\n />\n </LabeledField>\n </div>\n )}\n\n {step === 1 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Protocol & target</h3>\n <LabeledField label=\"Protocol\">\n <div className=\"flex gap-2\">\n {protocolOptions.map((proto) => (\n <button\n key={proto}\n type=\"button\"\n onClick={() => update('protocol', proto)}\n className={`rounded-md border px-4 py-2 text-sm ${\n state.protocol === proto\n ? 'border-action-primary-bg bg-bg-sunken text-text-primary'\n : 'border-border-subtle text-text-secondary hover:border-border-default'\n }`}\n >\n {proto}\n </button>\n ))}\n </div>\n </LabeledField>\n <LabeledField label=\"Local host\">\n <input\n type=\"text\"\n value={state.localHost}\n onChange={(e) => update('localHost', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n <LabeledField label=\"Local port\">\n <input\n type=\"number\"\n value={state.localPort}\n onChange={(e) => update('localPort', parseInt(e.target.value, 10) || 0)}\n min={1}\n max={65535}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n </div>\n )}\n\n {step === 2 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Hostname</h3>\n <div className=\"flex gap-2\">\n <HostnameKindButton\n active={state.hostnameKind === 'managed'}\n onClick={() => update('hostnameKind', 'managed')}\n disabled={!caps?.supportsManagedSubdomains}\n label=\"Managed subdomain\"\n hint=\"*.vibetunnels.com\"\n />\n <HostnameKindButton\n active={state.hostnameKind === 'custom'}\n onClick={() => update('hostnameKind', 'custom')}\n disabled={!caps?.supportsCustomDomains}\n label=\"Custom domain\"\n hint=\"TXT verification\"\n />\n <HostnameKindButton\n active={state.hostnameKind === 'auto'}\n onClick={() => update('hostnameKind', 'auto')}\n disabled={!supportsAutoHostname}\n label=\"Auto\"\n hint=\"provider default\"\n />\n </div>\n\n {state.hostnameKind === 'managed' && (\n <>\n <LabeledField label=\"Environment\">\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => update('environment', TunnelEnvironment.Prod)}\n className={`rounded-md border px-4 py-2 text-sm ${\n state.environment === TunnelEnvironment.Prod\n ? 'border-action-primary-bg bg-bg-sunken text-text-primary'\n : 'border-border-subtle text-text-secondary hover:border-border-default'\n }`}\n >\n Prod (vibetunnels.com)\n </button>\n <button\n type=\"button\"\n onClick={() => update('environment', TunnelEnvironment.Alpha)}\n className={`rounded-md border px-4 py-2 text-sm ${\n state.environment === TunnelEnvironment.Alpha\n ? 'border-action-primary-bg bg-bg-sunken text-text-primary'\n : 'border-border-subtle text-text-secondary hover:border-border-default'\n }`}\n >\n Alpha\n </button>\n </div>\n </LabeledField>\n <LabeledField label=\"Subdomain label\">\n <input\n type=\"text\"\n value={state.subdomain}\n onChange={(e) => update('subdomain', e.target.value.toLowerCase())}\n placeholder=\"demo\"\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n {state.subdomain && (\n <p className=\"text-xs text-text-secondary\">\n Will be reserved as{' '}\n <span className=\"font-mono text-text-primary\">\n {state.subdomain}\n {state.environment === TunnelEnvironment.Alpha\n ? '.alpha.vibetunnels.com'\n : '.vibetunnels.com'}\n </span>\n </p>\n )}\n </>\n )}\n\n {state.hostnameKind === 'custom' && (\n <LabeledField label=\"Custom domain\">\n <input\n type=\"text\"\n value={state.customDomain}\n onChange={(e) => update('customDomain', e.target.value.toLowerCase())}\n placeholder=\"preview.example.com\"\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <p className=\"text-xs text-text-muted mt-2\">\n Custom domains are registered on create but must be verified via a TXT record\n before they can be attached. You'll see the verification challenge in the\n Domains tab after creation.\n </p>\n </LabeledField>\n )}\n </div>\n )}\n\n {step === 3 && (\n <div className=\"space-y-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Policies (optional)</h3>\n <LabeledField label=\"Basic auth user\">\n <input\n type=\"text\"\n value={state.basicAuthUser}\n onChange={(e) => update('basicAuthUser', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n <LabeledField label=\"Basic auth password\">\n <input\n type=\"password\"\n value={state.basicAuthPassword}\n onChange={(e) => update('basicAuthPassword', e.target.value)}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </LabeledField>\n </div>\n )}\n\n {step === 4 && (\n <div className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('vibecontrols.tunnels.review', 'Review')}\n </h3>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm\">\n <dt className=\"text-text-muted\">Provider</dt>\n <dd className=\"text-text-primary\">{state.providerKey}</dd>\n <dt className=\"text-text-muted\">Name</dt>\n <dd className=\"text-text-primary\">{state.name}</dd>\n <dt className=\"text-text-muted\">Agent</dt>\n <dd className=\"text-text-primary font-mono\">{state.agentId}</dd>\n <dt className=\"text-text-muted\">Protocol</dt>\n <dd className=\"text-text-primary\">{state.protocol}</dd>\n <dt className=\"text-text-muted\">Target</dt>\n <dd className=\"text-text-primary font-mono\">\n {state.localHost}:{state.localPort}\n </dd>\n <dt className=\"text-text-muted\">Hostname</dt>\n <dd className=\"text-text-primary\">\n {state.hostnameKind === 'managed'\n ? `${state.subdomain}.${state.environment === 'ALPHA' ? 'alpha.' : ''}vibetunnels.com`\n : state.hostnameKind === 'custom'\n ? state.customDomain\n : 'auto-assigned'}\n </dd>\n <dt className=\"text-text-muted\">Basic auth</dt>\n <dd className=\"text-text-primary\">{state.basicAuthUser ? 'yes' : 'no'}</dd>\n </dl>\n <div className=\"border-t border-border-subtle pt-3\">\n <h4 className=\"mb-2 text-sm font-medium text-text-secondary\">\n {tr('vibecontrols.tunnels.tags', 'Tags')}\n </h4>\n <EntityTagPicker entityType=\"VIBECONTROLS_TUNNEL\" onDraftChange={setDraftTagIds} />\n </div>\n {submitError && (\n <div className=\"rounded-md bg-status-error-bg p-3 text-sm text-status-error-fg\">\n {submitError}\n </div>\n )}\n </div>\n )}\n </motion.div>\n </AnimatePresence>\n\n <div className=\"flex items-center justify-between mt-4\">\n <button\n type=\"button\"\n disabled={step === 0}\n onClick={back}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-elevated disabled:opacity-50\"\n >\n {tr('vibecontrols.tunnels.back', 'Back')}\n </button>\n {step < STEPS.length - 1 ? (\n <button\n type=\"button\"\n disabled={!canAdvance}\n onClick={next}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {tr('vibecontrols.tunnels.next', 'Next')}\n </button>\n ) : (\n <button\n type=\"button\"\n disabled={creating || isFlushingTags}\n onClick={() => void submit()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-fg hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {creating\n ? tr('vibecontrols.tunnels.creating', 'Creating...')\n : tr('vibecontrols.tunnels.createTunnel', 'Create tunnel')}\n </button>\n )}\n </div>\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing');\n }}\n {...getQuotaDialogContent('tunnel-subdomain')}\n />\n </div>\n );\n}\n\nfunction LabeledField({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <label className=\"block\">\n <span className=\"text-xs font-medium text-text-secondary\">{label}</span>\n <div className=\"mt-1\">{children}</div>\n </label>\n );\n}\n\nfunction HostnameKindButton({\n active,\n disabled,\n onClick,\n label,\n hint,\n}: {\n active: boolean;\n disabled?: boolean;\n onClick: () => void;\n label: string;\n hint: string;\n}) {\n return (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={onClick}\n className={`flex-1 rounded-md border p-3 text-left transition-colors ${\n active\n ? 'border-action-primary-bg bg-bg-sunken'\n : 'border-border-subtle hover:border-border-default'\n } ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}\n >\n <div className=\"text-sm font-medium text-text-primary\">{label}</div>\n <div className=\"text-xs text-text-muted mt-0.5\">{hint}</div>\n </button>\n );\n}\n"],"mappings":"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"}
@@ -130,7 +130,7 @@ function y() {
130
130
  return;
131
131
  }
132
132
  let n = t.data?.createAction?.id;
133
- n && await Ze(n), k({ type: "none" }), U("Action created", "success");
133
+ k({ type: "none" }), U("Action created", "success"), n && await Ze(n);
134
134
  } catch (e) {
135
135
  if (c(e)) {
136
136
  k({ type: "none" }), H("action");