@burdenoff/microfe-vibecontrols 2026.519.2 → 2026.519.4
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.
- package/dist/components/actions/ActionForm.js +138 -134
- package/dist/components/actions/ActionForm.js.map +1 -1
- package/dist/components/docs/DocsPageEditor.js +83 -69
- package/dist/components/docs/DocsPageEditor.js.map +1 -1
- package/dist/components/notes/NotesTabPanel.js +4 -5
- package/dist/components/notes/NotesTabPanel.js.map +1 -1
- package/dist/components/shared/EntityTagPicker.js +110 -81
- package/dist/components/shared/EntityTagPicker.js.map +1 -1
- package/dist/components/shared/TagFilter.js +4 -4
- package/dist/components/shared/index.js +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js +208 -187
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js +41 -37
- package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
- package/dist/pages/ActionsPage.js +225 -217
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +13 -13
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +248 -234
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogDocsPage.js +119 -105
- package/dist/pages/CatalogDocsPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +257 -243
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentDocsPage.js +119 -105
- package/dist/pages/ComponentDocsPage.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +4 -4
- package/dist/pages/TargetDetailsPage.js +136 -119
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +47 -33
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +161 -153
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +407 -407
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibeDocsPage.js +119 -105
- package/dist/pages/VibeDocsPage.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +95 -77
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -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';\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\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) navigate(`/vibecontrols/tunnels/${id}`);\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 {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 { useTagEntity } from '@/hooks/useEntityTags';\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 [draftTagIds, setDraftTagIds] = useState<string[]>([]);\n const [tagEntity] = useTagEntity();\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 // Persist any tags chosen during creation. Best-effort: the tunnel\n // already exists, so a tag failure must not block navigation —\n // the detail page picker lets the user retry.\n if (draftTagIds.length > 0) {\n try {\n await Promise.all(\n draftTagIds.map((tagId) =>\n tagEntity({\n variables: {\n input: { entityType: 'VIBECONTROLS_TUNNEL', entityId: id, tagId },\n },\n })\n )\n );\n } catch {\n // Swallow — tunnel creation succeeded; tags can be added later.\n }\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}\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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"}
|
|
@@ -75,14 +75,14 @@ var a = [
|
|
|
75
75
|
cols: 6,
|
|
76
76
|
rows: 4
|
|
77
77
|
}
|
|
78
|
-
], s = ({ initialData: s, onSubmit: c, onCancel: l, isLoading: u, title: d }) => {
|
|
79
|
-
let { t:
|
|
80
|
-
let n =
|
|
78
|
+
], s = ({ initialData: s, onSubmit: c, onCancel: l, isLoading: u, title: d, extraSection: f }) => {
|
|
79
|
+
let { t: p } = t(), m = (e, t) => {
|
|
80
|
+
let n = p(e);
|
|
81
81
|
return n === e ? t : n;
|
|
82
|
-
},
|
|
82
|
+
}, h = a.map((e) => ({
|
|
83
83
|
value: e.value,
|
|
84
|
-
label:
|
|
85
|
-
})), [
|
|
84
|
+
label: m(e.labelKey, e.fallback)
|
|
85
|
+
})), [g, _] = e({
|
|
86
86
|
name: s?.name || "",
|
|
87
87
|
description: s?.description || "",
|
|
88
88
|
icon: s?.icon || "",
|
|
@@ -107,48 +107,48 @@ var a = [
|
|
|
107
107
|
})]
|
|
108
108
|
}), /* @__PURE__ */ i("form", {
|
|
109
109
|
onSubmit: (e) => {
|
|
110
|
-
e.preventDefault(), c(
|
|
110
|
+
e.preventDefault(), c(g);
|
|
111
111
|
},
|
|
112
112
|
className: "p-6 space-y-4",
|
|
113
113
|
children: [
|
|
114
114
|
/* @__PURE__ */ i("div", { children: [/* @__PURE__ */ i("label", {
|
|
115
115
|
className: "block text-sm font-medium text-text-secondary mb-1",
|
|
116
|
-
children: [
|
|
116
|
+
children: [m("vibeDeck.name", "Name"), " *"]
|
|
117
117
|
}), /* @__PURE__ */ r("input", {
|
|
118
118
|
type: "text",
|
|
119
|
-
value:
|
|
120
|
-
onChange: (e) =>
|
|
121
|
-
...
|
|
119
|
+
value: g.name,
|
|
120
|
+
onChange: (e) => _({
|
|
121
|
+
...g,
|
|
122
122
|
name: e.target.value
|
|
123
123
|
}),
|
|
124
124
|
className: "w-full px-3 py-2 rounded-lg border border-border-default bg-bg-secondary text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg",
|
|
125
|
-
placeholder:
|
|
125
|
+
placeholder: m("vibeDeck.namePlaceholder", "My Vibe Deck"),
|
|
126
126
|
required: !0
|
|
127
127
|
})] }),
|
|
128
128
|
/* @__PURE__ */ i("div", { children: [/* @__PURE__ */ r("label", {
|
|
129
129
|
className: "block text-sm font-medium text-text-secondary mb-1",
|
|
130
|
-
children:
|
|
130
|
+
children: m("vibeDeck.description", "Description")
|
|
131
131
|
}), /* @__PURE__ */ r("textarea", {
|
|
132
|
-
value:
|
|
133
|
-
onChange: (e) =>
|
|
134
|
-
...
|
|
132
|
+
value: g.description,
|
|
133
|
+
onChange: (e) => _({
|
|
134
|
+
...g,
|
|
135
135
|
description: e.target.value
|
|
136
136
|
}),
|
|
137
137
|
className: "w-full px-3 py-2 rounded-lg border border-border-default bg-bg-secondary text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg resize-none",
|
|
138
138
|
rows: 2,
|
|
139
|
-
placeholder:
|
|
139
|
+
placeholder: m("vibeDeck.descriptionPlaceholder", "Optional description...")
|
|
140
140
|
})] }),
|
|
141
141
|
/* @__PURE__ */ i("div", { children: [/* @__PURE__ */ r("label", {
|
|
142
142
|
className: "block text-sm font-medium text-text-secondary mb-1",
|
|
143
|
-
children:
|
|
143
|
+
children: m("vibeDeck.colorTheme", "Color Theme")
|
|
144
144
|
}), /* @__PURE__ */ r("select", {
|
|
145
|
-
value:
|
|
146
|
-
onChange: (e) =>
|
|
147
|
-
...
|
|
145
|
+
value: g.colorTheme,
|
|
146
|
+
onChange: (e) => _({
|
|
147
|
+
...g,
|
|
148
148
|
colorTheme: e.target.value
|
|
149
149
|
}),
|
|
150
150
|
className: "w-full px-3 py-2 rounded-lg border border-border-default bg-bg-secondary text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg",
|
|
151
|
-
children:
|
|
151
|
+
children: h.map((e) => /* @__PURE__ */ r("option", {
|
|
152
152
|
value: e.value,
|
|
153
153
|
children: e.label
|
|
154
154
|
}, e.value))
|
|
@@ -156,18 +156,18 @@ var a = [
|
|
|
156
156
|
/* @__PURE__ */ i("div", { children: [
|
|
157
157
|
/* @__PURE__ */ r("label", {
|
|
158
158
|
className: "block text-sm font-medium text-text-secondary mb-2",
|
|
159
|
-
children:
|
|
159
|
+
children: m("vibeDeck.gridLayout", "Grid Layout")
|
|
160
160
|
}),
|
|
161
161
|
/* @__PURE__ */ r("div", {
|
|
162
162
|
className: "flex flex-wrap gap-2",
|
|
163
163
|
children: o.map((e) => /* @__PURE__ */ r("button", {
|
|
164
164
|
type: "button",
|
|
165
|
-
onClick: () =>
|
|
166
|
-
...
|
|
165
|
+
onClick: () => _({
|
|
166
|
+
...g,
|
|
167
167
|
gridColumns: e.cols,
|
|
168
168
|
gridRows: e.rows
|
|
169
169
|
}),
|
|
170
|
-
className: `px-3 py-1.5 rounded-lg text-sm border transition-colors ${
|
|
170
|
+
className: `px-3 py-1.5 rounded-lg text-sm border transition-colors ${g.gridColumns === e.cols && g.gridRows === e.rows ? "border-action-primary-bg bg-action-primary-bg/10 text-action-primary-bg" : "border-border-default text-text-secondary hover:bg-bg-tertiary"}`,
|
|
171
171
|
children: e.label
|
|
172
172
|
}, e.label))
|
|
173
173
|
}),
|
|
@@ -177,14 +177,14 @@ var a = [
|
|
|
177
177
|
className: "flex-1",
|
|
178
178
|
children: [/* @__PURE__ */ r("label", {
|
|
179
179
|
className: "text-xs text-text-tertiary",
|
|
180
|
-
children:
|
|
180
|
+
children: m("vibeDeck.columns", "Columns")
|
|
181
181
|
}), /* @__PURE__ */ r("input", {
|
|
182
182
|
type: "number",
|
|
183
183
|
min: 1,
|
|
184
184
|
max: 10,
|
|
185
|
-
value:
|
|
186
|
-
onChange: (e) =>
|
|
187
|
-
...
|
|
185
|
+
value: g.gridColumns,
|
|
186
|
+
onChange: (e) => _({
|
|
187
|
+
...g,
|
|
188
188
|
gridColumns: parseInt(e.target.value) || 4
|
|
189
189
|
}),
|
|
190
190
|
className: "w-full px-2 py-1 rounded border border-border-default bg-bg-secondary text-text-primary text-sm"
|
|
@@ -193,14 +193,14 @@ var a = [
|
|
|
193
193
|
className: "flex-1",
|
|
194
194
|
children: [/* @__PURE__ */ r("label", {
|
|
195
195
|
className: "text-xs text-text-tertiary",
|
|
196
|
-
children:
|
|
196
|
+
children: m("vibeDeck.rows", "Rows")
|
|
197
197
|
}), /* @__PURE__ */ r("input", {
|
|
198
198
|
type: "number",
|
|
199
199
|
min: 1,
|
|
200
200
|
max: 10,
|
|
201
|
-
value:
|
|
202
|
-
onChange: (e) =>
|
|
203
|
-
...
|
|
201
|
+
value: g.gridRows,
|
|
202
|
+
onChange: (e) => _({
|
|
203
|
+
...g,
|
|
204
204
|
gridRows: parseInt(e.target.value) || 3
|
|
205
205
|
}),
|
|
206
206
|
className: "w-full px-2 py-1 rounded border border-border-default bg-bg-secondary text-text-primary text-sm"
|
|
@@ -208,18 +208,22 @@ var a = [
|
|
|
208
208
|
})]
|
|
209
209
|
})
|
|
210
210
|
] }),
|
|
211
|
+
f && /* @__PURE__ */ r("div", {
|
|
212
|
+
className: "border-t border-border-default pt-4",
|
|
213
|
+
children: f
|
|
214
|
+
}),
|
|
211
215
|
/* @__PURE__ */ i("div", {
|
|
212
216
|
className: "flex justify-end gap-3 pt-2",
|
|
213
217
|
children: [/* @__PURE__ */ r("button", {
|
|
214
218
|
type: "button",
|
|
215
219
|
onClick: l,
|
|
216
220
|
className: "px-4 py-2 rounded-lg text-sm text-text-secondary hover:bg-bg-tertiary transition-colors",
|
|
217
|
-
children:
|
|
221
|
+
children: m("vibeDeck.cancel", "Cancel")
|
|
218
222
|
}), /* @__PURE__ */ r("button", {
|
|
219
223
|
type: "submit",
|
|
220
|
-
disabled: u || !
|
|
224
|
+
disabled: u || !g.name.trim(),
|
|
221
225
|
className: "px-4 py-2 rounded-lg text-sm bg-action-primary-bg text-action-primary-text hover:opacity-90 transition-opacity disabled:opacity-50",
|
|
222
|
-
children: u ?
|
|
226
|
+
children: u ? m("vibeDeck.saving", "Saving...") : m("vibeDeck.save", "Save")
|
|
223
227
|
})]
|
|
224
228
|
})
|
|
225
229
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeDeckForm.js","names":[],"sources":["../../../src/components/vibedeck/VibeDeckForm.tsx"],"sourcesContent":["import { type FC, useState } from 'react';\nimport { X } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface VibeDeckFormData {\n name: string;\n description: string;\n icon: string;\n colorTheme: string;\n gridColumns: number;\n gridRows: number;\n buttonSize: string;\n}\n\ninterface VibeDeckFormProps {\n initialData?: Partial<VibeDeckFormData>;\n onSubmit: (data: VibeDeckFormData) => void;\n onCancel: () => void;\n isLoading: boolean;\n title: string;\n}\n\nconst colorOptionKeys: { value: string; labelKey: string; fallback: string }[] = [\n { value: '', labelKey: 'vibeDeck.colorDefault', fallback: 'Default' },\n { value: 'blue', labelKey: 'vibeDeck.colorBlue', fallback: 'Blue' },\n { value: 'green', labelKey: 'vibeDeck.colorGreen', fallback: 'Green' },\n { value: 'red', labelKey: 'vibeDeck.colorRed', fallback: 'Red' },\n { value: 'purple', labelKey: 'vibeDeck.colorPurple', fallback: 'Purple' },\n { value: 'orange', labelKey: 'vibeDeck.colorOrange', fallback: 'Orange' },\n { value: 'cyan', labelKey: 'vibeDeck.colorCyan', fallback: 'Cyan' },\n { value: 'pink', labelKey: 'vibeDeck.colorPink', fallback: 'Pink' },\n];\n\nconst gridPresets = [\n { label: '3x3', cols: 3, rows: 3 },\n { label: '4x3', cols: 4, rows: 3 },\n { label: '4x4', cols: 4, rows: 4 },\n { label: '5x3', cols: 5, rows: 3 },\n { label: '5x4', cols: 5, rows: 4 },\n { label: '6x4', cols: 6, rows: 4 },\n];\n\nexport const VibeDeckForm: FC<VibeDeckFormProps> = ({\n initialData,\n onSubmit,\n onCancel,\n isLoading,\n title,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const colorOptions = colorOptionKeys.map((opt) => ({\n value: opt.value,\n label: tr(opt.labelKey, opt.fallback),\n }));\n\n const [form, setForm] = useState<VibeDeckFormData>({\n name: initialData?.name || '',\n description: initialData?.description || '',\n icon: initialData?.icon || '',\n colorTheme: initialData?.colorTheme || '',\n gridColumns: initialData?.gridColumns || 4,\n gridRows: initialData?.gridRows || 3,\n buttonSize: initialData?.buttonSize || 'md',\n });\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n onSubmit(form);\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay backdrop-blur-sm\">\n <div className=\"bg-bg-elevated border border-border-default rounded-xl shadow-xl w-full max-w-md mx-4\">\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n <button\n onClick={onCancel}\n className=\"p-1 rounded hover:bg-bg-tertiary text-text-tertiary\"\n >\n <X size={18} />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-6 space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr('vibeDeck.name', 'Name')} *\n </label>\n <input\n type=\"text\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-secondary text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n placeholder={tr('vibeDeck.namePlaceholder', 'My Vibe Deck')}\n required\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr('vibeDeck.description', 'Description')}\n </label>\n <textarea\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-secondary text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg resize-none\"\n rows={2}\n placeholder={tr('vibeDeck.descriptionPlaceholder', 'Optional description...')}\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr('vibeDeck.colorTheme', 'Color Theme')}\n </label>\n <select\n value={form.colorTheme}\n onChange={(e) => setForm({ ...form, colorTheme: e.target.value })}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-secondary text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n {colorOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-2\">\n {tr('vibeDeck.gridLayout', 'Grid Layout')}\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {gridPresets.map((preset) => {\n const isActive = form.gridColumns === preset.cols && form.gridRows === preset.rows;\n return (\n <button\n key={preset.label}\n type=\"button\"\n onClick={() =>\n setForm({ ...form, gridColumns: preset.cols, gridRows: preset.rows })\n }\n className={`px-3 py-1.5 rounded-lg text-sm border transition-colors ${\n isActive\n ? 'border-action-primary-bg bg-action-primary-bg/10 text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-tertiary'\n }`}\n >\n {preset.label}\n </button>\n );\n })}\n </div>\n <div className=\"flex gap-2 mt-2\">\n <div className=\"flex-1\">\n <label className=\"text-xs text-text-tertiary\">\n {tr('vibeDeck.columns', 'Columns')}\n </label>\n <input\n type=\"number\"\n min={1}\n max={10}\n value={form.gridColumns}\n onChange={(e) => setForm({ ...form, gridColumns: parseInt(e.target.value) || 4 })}\n className=\"w-full px-2 py-1 rounded border border-border-default bg-bg-secondary text-text-primary text-sm\"\n />\n </div>\n <div className=\"flex-1\">\n <label className=\"text-xs text-text-tertiary\">{tr('vibeDeck.rows', 'Rows')}</label>\n <input\n type=\"number\"\n min={1}\n max={10}\n value={form.gridRows}\n onChange={(e) => setForm({ ...form, gridRows: parseInt(e.target.value) || 3 })}\n className=\"w-full px-2 py-1 rounded border border-border-default bg-bg-secondary text-text-primary text-sm\"\n />\n </div>\n </div>\n </div>\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"px-4 py-2 rounded-lg text-sm text-text-secondary hover:bg-bg-tertiary transition-colors\"\n >\n {tr('vibeDeck.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading || !form.name.trim()}\n className=\"px-4 py-2 rounded-lg text-sm bg-action-primary-bg text-action-primary-text hover:opacity-90 transition-opacity disabled:opacity-50\"\n >\n {isLoading ? tr('vibeDeck.saving', 'Saving...') : tr('vibeDeck.save', 'Save')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VibeDeckForm.js","names":[],"sources":["../../../src/components/vibedeck/VibeDeckForm.tsx"],"sourcesContent":["import { type FC, type ReactNode, useState } from 'react';\nimport { X } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface VibeDeckFormData {\n name: string;\n description: string;\n icon: string;\n colorTheme: string;\n gridColumns: number;\n gridRows: number;\n buttonSize: string;\n}\n\ninterface VibeDeckFormProps {\n initialData?: Partial<VibeDeckFormData>;\n onSubmit: (data: VibeDeckFormData) => void;\n onCancel: () => void;\n isLoading: boolean;\n title: string;\n /** Optional extra content rendered below the form fields (e.g. tag picker in edit mode) */\n extraSection?: ReactNode;\n}\n\nconst colorOptionKeys: { value: string; labelKey: string; fallback: string }[] = [\n { value: '', labelKey: 'vibeDeck.colorDefault', fallback: 'Default' },\n { value: 'blue', labelKey: 'vibeDeck.colorBlue', fallback: 'Blue' },\n { value: 'green', labelKey: 'vibeDeck.colorGreen', fallback: 'Green' },\n { value: 'red', labelKey: 'vibeDeck.colorRed', fallback: 'Red' },\n { value: 'purple', labelKey: 'vibeDeck.colorPurple', fallback: 'Purple' },\n { value: 'orange', labelKey: 'vibeDeck.colorOrange', fallback: 'Orange' },\n { value: 'cyan', labelKey: 'vibeDeck.colorCyan', fallback: 'Cyan' },\n { value: 'pink', labelKey: 'vibeDeck.colorPink', fallback: 'Pink' },\n];\n\nconst gridPresets = [\n { label: '3x3', cols: 3, rows: 3 },\n { label: '4x3', cols: 4, rows: 3 },\n { label: '4x4', cols: 4, rows: 4 },\n { label: '5x3', cols: 5, rows: 3 },\n { label: '5x4', cols: 5, rows: 4 },\n { label: '6x4', cols: 6, rows: 4 },\n];\n\nexport const VibeDeckForm: FC<VibeDeckFormProps> = ({\n initialData,\n onSubmit,\n onCancel,\n isLoading,\n title,\n extraSection,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const colorOptions = colorOptionKeys.map((opt) => ({\n value: opt.value,\n label: tr(opt.labelKey, opt.fallback),\n }));\n\n const [form, setForm] = useState<VibeDeckFormData>({\n name: initialData?.name || '',\n description: initialData?.description || '',\n icon: initialData?.icon || '',\n colorTheme: initialData?.colorTheme || '',\n gridColumns: initialData?.gridColumns || 4,\n gridRows: initialData?.gridRows || 3,\n buttonSize: initialData?.buttonSize || 'md',\n });\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n onSubmit(form);\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay backdrop-blur-sm\">\n <div className=\"bg-bg-elevated border border-border-default rounded-xl shadow-xl w-full max-w-md mx-4\">\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-default\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n <button\n onClick={onCancel}\n className=\"p-1 rounded hover:bg-bg-tertiary text-text-tertiary\"\n >\n <X size={18} />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-6 space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr('vibeDeck.name', 'Name')} *\n </label>\n <input\n type=\"text\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-secondary text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n placeholder={tr('vibeDeck.namePlaceholder', 'My Vibe Deck')}\n required\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr('vibeDeck.description', 'Description')}\n </label>\n <textarea\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-secondary text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg resize-none\"\n rows={2}\n placeholder={tr('vibeDeck.descriptionPlaceholder', 'Optional description...')}\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-1\">\n {tr('vibeDeck.colorTheme', 'Color Theme')}\n </label>\n <select\n value={form.colorTheme}\n onChange={(e) => setForm({ ...form, colorTheme: e.target.value })}\n className=\"w-full px-3 py-2 rounded-lg border border-border-default bg-bg-secondary text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n {colorOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-secondary mb-2\">\n {tr('vibeDeck.gridLayout', 'Grid Layout')}\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {gridPresets.map((preset) => {\n const isActive = form.gridColumns === preset.cols && form.gridRows === preset.rows;\n return (\n <button\n key={preset.label}\n type=\"button\"\n onClick={() =>\n setForm({ ...form, gridColumns: preset.cols, gridRows: preset.rows })\n }\n className={`px-3 py-1.5 rounded-lg text-sm border transition-colors ${\n isActive\n ? 'border-action-primary-bg bg-action-primary-bg/10 text-action-primary-bg'\n : 'border-border-default text-text-secondary hover:bg-bg-tertiary'\n }`}\n >\n {preset.label}\n </button>\n );\n })}\n </div>\n <div className=\"flex gap-2 mt-2\">\n <div className=\"flex-1\">\n <label className=\"text-xs text-text-tertiary\">\n {tr('vibeDeck.columns', 'Columns')}\n </label>\n <input\n type=\"number\"\n min={1}\n max={10}\n value={form.gridColumns}\n onChange={(e) => setForm({ ...form, gridColumns: parseInt(e.target.value) || 4 })}\n className=\"w-full px-2 py-1 rounded border border-border-default bg-bg-secondary text-text-primary text-sm\"\n />\n </div>\n <div className=\"flex-1\">\n <label className=\"text-xs text-text-tertiary\">{tr('vibeDeck.rows', 'Rows')}</label>\n <input\n type=\"number\"\n min={1}\n max={10}\n value={form.gridRows}\n onChange={(e) => setForm({ ...form, gridRows: parseInt(e.target.value) || 3 })}\n className=\"w-full px-2 py-1 rounded border border-border-default bg-bg-secondary text-text-primary text-sm\"\n />\n </div>\n </div>\n </div>\n\n {extraSection && (\n <div className=\"border-t border-border-default pt-4\">{extraSection}</div>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"px-4 py-2 rounded-lg text-sm text-text-secondary hover:bg-bg-tertiary transition-colors\"\n >\n {tr('vibeDeck.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading || !form.name.trim()}\n className=\"px-4 py-2 rounded-lg text-sm bg-action-primary-bg text-action-primary-text hover:opacity-90 transition-opacity disabled:opacity-50\"\n >\n {isLoading ? tr('vibeDeck.saving', 'Saving...') : tr('vibeDeck.save', 'Save')}\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|