@burdenoff/microfe-vibecontrols 2026.601.23 → 2026.602.2
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/tunnels/TunnelCreateWizard.js +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/components/vibes/DeleteVibeDialog.js +130 -0
- package/dist/components/vibes/DeleteVibeDialog.js.map +1 -0
- package/dist/components/vibes/VibeCard.js +64 -56
- package/dist/components/vibes/VibeCard.js.map +1 -1
- package/dist/components/vibes/VibeList.js +132 -113
- package/dist/components/vibes/VibeList.js.map +1 -1
- package/dist/components/vibes/index.js +1 -0
- package/dist/generated/wspace-operations.js +2 -2
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +560 -523
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibesPage.js +311 -280
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +192 -80
- 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 { Stepper } from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\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 {\n getQuotaDialogContent,\n markQuotaHandledLocally,\n isQuotaExhaustedError,\n} from '@/utils/quotaUtils';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { useDraftEntityTags } from '@/hooks/useDraftEntityTags';\nimport {\n LinkToVibePanel,\n LINK_TO_VIBE_DEFAULT,\n linkToVibeInput,\n type LinkToVibeValue,\n} from '@/components/shared';\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 bus = useEventBus();\n const { workspaceId } = useVibeControls();\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 // G7: Optional link to a Vibe + visibility, threaded into CreateTunnelInput.\n const [vibeLink, setVibeLink] = useState<LinkToVibeValue>(LINK_TO_VIBE_DEFAULT);\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 ...linkToVibeInput(vibeLink),\n };\n const result = await createTunnel({ variables: { input } });\n\n if (result.error) {\n if (isQuotaExhaustedError(result.error)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n bus.emitCustom('vibecontrols.quota.tunnel_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\n return;\n }\n\n setSubmitError(result.error.message);\n return;\n }\n\n const id = result.data?.createTunnel.id;\n if (id) {\n bus.emitCustom('vibecontrols.tunnel.created', {\n workspaceId: workspaceId ?? undefined,\n tunnelId: id,\n });\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 markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n bus.emitCustom('vibecontrols.quota.tunnel_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\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 <Stepper\n className=\"mb-6\"\n active={step}\n onStepClick={(i) => i <= step && setStep(i)}\n steps={STEPS.map((s) => ({ key: s.id, label: s.label }))}\n />\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 aria-label=\"My dev tunnel\"\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 aria-label=\"control\"\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 aria-label=\"control\"\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 aria-label=\"control\"\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 {caps?.supportsManagedSubdomains && (\n <HostnameKindButton\n active={state.hostnameKind === 'managed'}\n onClick={() => update('hostnameKind', 'managed')}\n label=\"Managed subdomain\"\n hint=\"*.vibetunnels.com\"\n />\n )}\n {caps?.supportsCustomDomains && (\n <HostnameKindButton\n active={state.hostnameKind === 'custom'}\n onClick={() => update('hostnameKind', 'custom')}\n label=\"Custom domain\"\n hint=\"TXT verification\"\n />\n )}\n {supportsAutoHostname && (\n <HostnameKindButton\n active={state.hostnameKind === 'auto'}\n onClick={() => update('hostnameKind', 'auto')}\n label=\"Auto\"\n hint=\"assigned by Cloudflare\"\n />\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 aria-label=\"demo\"\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 aria-label=\"preview.example.com\"\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 aria-label=\"control\"\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 aria-label=\"control\"\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 <LinkToVibePanel value={vibeLink} onChange={setVibeLink} disabled={creating} />\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/plans');\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 onClick,\n label,\n hint,\n}: {\n active: boolean;\n onClick: () => void;\n label: string;\n hint: string;\n}) {\n return (\n <button\n type=\"button\"\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 }`}\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 { Stepper } from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\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 {\n getQuotaDialogContent,\n markQuotaHandledLocally,\n isQuotaExhaustedError,\n} from '@/utils/quotaUtils';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { useDraftEntityTags } from '@/hooks/useDraftEntityTags';\nimport {\n LinkToVibePanel,\n LINK_TO_VIBE_DEFAULT,\n linkToVibeInput,\n type LinkToVibeValue,\n} from '@/components/shared';\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 bus = useEventBus();\n const { workspaceId } = useVibeControls();\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 // G7: Optional link to a Vibe + visibility, threaded into CreateTunnelInput.\n const [vibeLink, setVibeLink] = useState<LinkToVibeValue>(LINK_TO_VIBE_DEFAULT);\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 ...linkToVibeInput(vibeLink),\n };\n const result = await createTunnel({ variables: { input } });\n\n if (result.error) {\n if (isQuotaExhaustedError(result.error)) {\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n bus.emitCustom('vibecontrols.quota.tunnel_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\n return;\n }\n\n setSubmitError(result.error.message);\n return;\n }\n\n const id = result.data?.createTunnel.id;\n if (id) {\n bus.emitCustom('vibecontrols.tunnel.created', {\n workspaceId: workspaceId ?? undefined,\n tunnelId: id,\n });\n setIsFlushingTags(true);\n try {\n await flushTags(id);\n } finally {\n setIsFlushingTags(false);\n }\n // Land on the detail page and immediately prompt to start — a freshly\n // created tunnel is a DRAFT, so offer to bring it online instead of\n // making the user hunt for the Start button.\n navigate(`/vibecontrols/tunnels/${id}`, { state: { autoPromptStart: true } });\n }\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setSubmitError(null);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n bus.emitCustom('vibecontrols.quota.tunnel_limit_hit', {\n workspaceId: workspaceId ?? undefined,\n });\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 <Stepper\n className=\"mb-6\"\n active={step}\n onStepClick={(i) => i <= step && setStep(i)}\n steps={STEPS.map((s) => ({ key: s.id, label: s.label }))}\n />\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 aria-label=\"My dev tunnel\"\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 aria-label=\"control\"\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 aria-label=\"control\"\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 aria-label=\"control\"\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 {caps?.supportsManagedSubdomains && (\n <HostnameKindButton\n active={state.hostnameKind === 'managed'}\n onClick={() => update('hostnameKind', 'managed')}\n label=\"Managed subdomain\"\n hint=\"*.vibetunnels.com\"\n />\n )}\n {caps?.supportsCustomDomains && (\n <HostnameKindButton\n active={state.hostnameKind === 'custom'}\n onClick={() => update('hostnameKind', 'custom')}\n label=\"Custom domain\"\n hint=\"TXT verification\"\n />\n )}\n {supportsAutoHostname && (\n <HostnameKindButton\n active={state.hostnameKind === 'auto'}\n onClick={() => update('hostnameKind', 'auto')}\n label=\"Auto\"\n hint=\"assigned by Cloudflare\"\n />\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 aria-label=\"demo\"\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 aria-label=\"preview.example.com\"\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 aria-label=\"control\"\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 aria-label=\"control\"\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 <LinkToVibePanel value={vibeLink} onChange={setVibeLink} disabled={creating} />\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/plans');\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 onClick,\n label,\n hint,\n}: {\n active: boolean;\n onClick: () => void;\n label: string;\n hint: string;\n}) {\n return (\n <button\n type=\"button\"\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 }`}\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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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { useEffect as e, useMemo as t, useRef as n, useState as r } from "react";
|
|
2
|
+
import { useI18n as i } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
3
|
+
import { AlertTriangle as a, Loader2 as o, Trash2 as s } from "lucide-react";
|
|
4
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/vibes/DeleteVibeDialog.tsx
|
|
6
|
+
var u = ({ vibe: u, deleting: d = !1, onConfirm: f, onCancel: p }) => {
|
|
7
|
+
let { t: m } = i(), h = (e, t) => {
|
|
8
|
+
let n = m(e);
|
|
9
|
+
return n === e ? t : n;
|
|
10
|
+
}, [g, _] = r(!1), [v, y] = r(""), b = n(null);
|
|
11
|
+
e(() => {
|
|
12
|
+
if (u) {
|
|
13
|
+
_(!1), y("");
|
|
14
|
+
let e = window.setTimeout(() => b.current?.focus(), 0);
|
|
15
|
+
return () => window.clearTimeout(e);
|
|
16
|
+
}
|
|
17
|
+
}, [u]);
|
|
18
|
+
let x = t(() => !!u && v.trim() === u.name, [v, u]) && !d;
|
|
19
|
+
if (!u) return null;
|
|
20
|
+
let S = () => {
|
|
21
|
+
x && f(g);
|
|
22
|
+
};
|
|
23
|
+
return /* @__PURE__ */ c("div", {
|
|
24
|
+
tabIndex: 0,
|
|
25
|
+
onKeyDown: (e) => {
|
|
26
|
+
e.key === "Escape" && !d && (e.preventDefault(), p());
|
|
27
|
+
},
|
|
28
|
+
role: "dialog",
|
|
29
|
+
"aria-modal": "true",
|
|
30
|
+
"aria-labelledby": "delete-vibe-title",
|
|
31
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4",
|
|
32
|
+
onClick: (e) => {
|
|
33
|
+
e.target === e.currentTarget && !d && p();
|
|
34
|
+
},
|
|
35
|
+
children: /* @__PURE__ */ l("div", {
|
|
36
|
+
className: "w-full max-w-md rounded-lg bg-bg-elevated border border-border-default shadow-xl p-5",
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ l("div", {
|
|
39
|
+
className: "flex items-start gap-3 mb-3",
|
|
40
|
+
children: [/* @__PURE__ */ c("div", {
|
|
41
|
+
className: "flex-shrink-0 size-9 rounded-full bg-status-error-bg flex items-center justify-center",
|
|
42
|
+
children: /* @__PURE__ */ c(s, { className: "size-4 text-status-error-text" })
|
|
43
|
+
}), /* @__PURE__ */ l("div", {
|
|
44
|
+
className: "min-w-0",
|
|
45
|
+
children: [/* @__PURE__ */ c("h2", {
|
|
46
|
+
id: "delete-vibe-title",
|
|
47
|
+
className: "text-base font-semibold text-text-primary",
|
|
48
|
+
children: h("vibeDelete.title", "Delete this vibe?")
|
|
49
|
+
}), /* @__PURE__ */ c("p", {
|
|
50
|
+
className: "text-sm text-text-secondary mt-1",
|
|
51
|
+
children: h("vibeDelete.body", "This permanently deletes the vibe and all of its sub-vibes. This action cannot be undone.")
|
|
52
|
+
})]
|
|
53
|
+
})]
|
|
54
|
+
}),
|
|
55
|
+
/* @__PURE__ */ c("p", {
|
|
56
|
+
className: "text-xs text-text-secondary mb-4",
|
|
57
|
+
children: h("vibeDelete.detachNote", "By default, related items (sessions, notes, tunnels, decks, actions, AI prompts & contexts, instruction files) are kept and simply detached from this vibe.")
|
|
58
|
+
}),
|
|
59
|
+
/* @__PURE__ */ l("label", {
|
|
60
|
+
className: "flex items-start gap-2 mb-3 cursor-pointer select-none",
|
|
61
|
+
children: [/* @__PURE__ */ c("input", {
|
|
62
|
+
type: "checkbox",
|
|
63
|
+
checked: g,
|
|
64
|
+
disabled: d,
|
|
65
|
+
onChange: (e) => _(e.target.checked),
|
|
66
|
+
className: "mt-0.5 size-4 rounded border-border-default cursor-pointer"
|
|
67
|
+
}), /* @__PURE__ */ c("span", {
|
|
68
|
+
className: "text-sm text-text-primary",
|
|
69
|
+
children: h("vibeDelete.alsoDeleteRelated", "Also permanently delete all related items")
|
|
70
|
+
})]
|
|
71
|
+
}),
|
|
72
|
+
g && /* @__PURE__ */ l("div", {
|
|
73
|
+
className: "flex items-start gap-2 mb-4 rounded-md border border-status-error-border bg-status-error-bg px-3 py-2",
|
|
74
|
+
children: [/* @__PURE__ */ c(a, { className: "size-4 mt-0.5 flex-shrink-0 text-status-error-text" }), /* @__PURE__ */ c("p", {
|
|
75
|
+
className: "text-xs text-status-error-text",
|
|
76
|
+
children: h("vibeDelete.relatedWarning", "Every session, note, tunnel, deck, action, AI prompt, AI context and instruction file linked to this vibe will be permanently deleted. This is irreversible.")
|
|
77
|
+
})]
|
|
78
|
+
}),
|
|
79
|
+
/* @__PURE__ */ l("label", {
|
|
80
|
+
htmlFor: "delete-vibe-confirm-name",
|
|
81
|
+
className: "block text-xs font-medium text-text-secondary mb-1",
|
|
82
|
+
children: [
|
|
83
|
+
h("vibeDelete.retypePrompt", "Type the vibe name to confirm:"),
|
|
84
|
+
" ",
|
|
85
|
+
/* @__PURE__ */ c("span", {
|
|
86
|
+
className: "font-mono text-text-primary break-all",
|
|
87
|
+
children: u.name
|
|
88
|
+
})
|
|
89
|
+
]
|
|
90
|
+
}),
|
|
91
|
+
/* @__PURE__ */ c("input", {
|
|
92
|
+
id: "delete-vibe-confirm-name",
|
|
93
|
+
ref: b,
|
|
94
|
+
type: "text",
|
|
95
|
+
value: v,
|
|
96
|
+
disabled: d,
|
|
97
|
+
onChange: (e) => y(e.target.value),
|
|
98
|
+
onKeyDown: (e) => {
|
|
99
|
+
e.key === "Enter" && (e.preventDefault(), S());
|
|
100
|
+
},
|
|
101
|
+
autoComplete: "off",
|
|
102
|
+
spellCheck: !1,
|
|
103
|
+
"aria-label": h("vibeDelete.retypeAria", "Vibe name confirmation"),
|
|
104
|
+
placeholder: u.name,
|
|
105
|
+
className: "w-full text-sm px-3 py-2 mb-4 rounded-md bg-bg-surface border border-border-default text-text-primary placeholder:text-text-tertiary focus:outline-none focus:ring-2 focus:ring-status-error-text/40"
|
|
106
|
+
}),
|
|
107
|
+
/* @__PURE__ */ l("div", {
|
|
108
|
+
className: "flex justify-end gap-2",
|
|
109
|
+
children: [/* @__PURE__ */ c("button", {
|
|
110
|
+
type: "button",
|
|
111
|
+
onClick: p,
|
|
112
|
+
disabled: d,
|
|
113
|
+
className: "text-sm px-3 py-1.5 rounded-md border border-border-default text-text-primary hover:bg-action-ghost-bg-hover disabled:opacity-50",
|
|
114
|
+
children: h("common.cancel", "Cancel")
|
|
115
|
+
}), /* @__PURE__ */ l("button", {
|
|
116
|
+
type: "button",
|
|
117
|
+
onClick: S,
|
|
118
|
+
disabled: !x,
|
|
119
|
+
className: "inline-flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-md bg-status-error-bg text-status-error-text hover:opacity-80 disabled:opacity-40 disabled:cursor-not-allowed",
|
|
120
|
+
children: [d ? /* @__PURE__ */ c(o, { className: "size-4 animate-spin" }) : /* @__PURE__ */ c(s, { className: "size-4" }), g ? h("vibeDelete.confirmAll", "Delete vibe & related") : h("vibeDelete.confirm", "Delete vibe")]
|
|
121
|
+
})]
|
|
122
|
+
})
|
|
123
|
+
]
|
|
124
|
+
})
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
//#endregion
|
|
128
|
+
export { u as DeleteVibeDialog };
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=DeleteVibeDialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeleteVibeDialog.js","names":[],"sources":["../../../src/components/vibes/DeleteVibeDialog.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AlertTriangle, Loader2, Trash2 } from 'lucide-react';\nimport { type FC, useEffect, useMemo, useRef, useState } from 'react';\n\n/** Minimal vibe shape the delete dialog needs from any call site. */\nexport interface DeletableVibe {\n id: string;\n name: string;\n}\n\nexport interface DeleteVibeDialogProps {\n /** The vibe being deleted. When null the dialog is closed. */\n vibe: DeletableVibe | null;\n /** Whether a delete request is currently in flight. */\n deleting?: boolean;\n /**\n * Invoked once the user has confirmed. `deleteRelatedEntities` reflects the\n * opt-in checkbox: when true the linked entities (sessions, notes, tunnels,\n * decks, actions, AI prompts/contexts, instruction files) are deleted too;\n * when false they are preserved and merely detached.\n */\n onConfirm: (deleteRelatedEntities: boolean) => void | Promise<void>;\n onCancel: () => void;\n}\n\n/**\n * Confirmation dialog for permanently deleting a vibe.\n *\n * Deleting a vibe is irreversible, so the user must retype the vibe's exact\n * name before the destructive action is enabled. An explicit, unchecked-by-\n * default checkbox lets them also delete every related entity; ticking it\n * surfaces an extra danger warning so the heightened blast radius is\n * unmistakable before they confirm.\n */\nexport const DeleteVibeDialog: FC<DeleteVibeDialogProps> = ({\n vibe,\n deleting = false,\n onConfirm,\n onCancel,\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 [deleteRelated, setDeleteRelated] = useState(false);\n const [confirmName, setConfirmName] = useState('');\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n // Reset transient state whenever the dialog opens for a (different) vibe so a\n // previous attempt never leaks into the next one.\n useEffect(() => {\n if (vibe) {\n setDeleteRelated(false);\n setConfirmName('');\n // Focus the name field so the irreversible gate is front-and-centre.\n const id = window.setTimeout(() => inputRef.current?.focus(), 0);\n return () => window.clearTimeout(id);\n }\n return undefined;\n }, [vibe]);\n\n const nameMatches = useMemo(\n () => !!vibe && confirmName.trim() === vibe.name,\n [confirmName, vibe]\n );\n const canDelete = nameMatches && !deleting;\n\n if (!vibe) return null;\n\n const handleConfirm = () => {\n if (!canDelete) return;\n void onConfirm(deleteRelated);\n };\n\n return (\n <div\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Escape' && !deleting) {\n e.preventDefault();\n onCancel();\n }\n }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"delete-vibe-title\"\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\"\n onClick={(e) => {\n if (e.target === e.currentTarget && !deleting) onCancel();\n }}\n >\n <div className=\"w-full max-w-md rounded-lg bg-bg-elevated border border-border-default shadow-xl p-5\">\n <div className=\"flex items-start gap-3 mb-3\">\n <div className=\"flex-shrink-0 size-9 rounded-full bg-status-error-bg flex items-center justify-center\">\n <Trash2 className=\"size-4 text-status-error-text\" />\n </div>\n <div className=\"min-w-0\">\n <h2 id=\"delete-vibe-title\" className=\"text-base font-semibold text-text-primary\">\n {tr('vibeDelete.title', 'Delete this vibe?')}\n </h2>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'vibeDelete.body',\n 'This permanently deletes the vibe and all of its sub-vibes. This action cannot be undone.'\n )}\n </p>\n </div>\n </div>\n\n <p className=\"text-xs text-text-secondary mb-4\">\n {tr(\n 'vibeDelete.detachNote',\n 'By default, related items (sessions, notes, tunnels, decks, actions, AI prompts & contexts, instruction files) are kept and simply detached from this vibe.'\n )}\n </p>\n\n <label className=\"flex items-start gap-2 mb-3 cursor-pointer select-none\">\n <input\n type=\"checkbox\"\n checked={deleteRelated}\n disabled={deleting}\n onChange={(e) => setDeleteRelated(e.target.checked)}\n className=\"mt-0.5 size-4 rounded border-border-default cursor-pointer\"\n />\n <span className=\"text-sm text-text-primary\">\n {tr('vibeDelete.alsoDeleteRelated', 'Also permanently delete all related items')}\n </span>\n </label>\n\n {deleteRelated && (\n <div className=\"flex items-start gap-2 mb-4 rounded-md border border-status-error-border bg-status-error-bg px-3 py-2\">\n <AlertTriangle className=\"size-4 mt-0.5 flex-shrink-0 text-status-error-text\" />\n <p className=\"text-xs text-status-error-text\">\n {tr(\n 'vibeDelete.relatedWarning',\n 'Every session, note, tunnel, deck, action, AI prompt, AI context and instruction file linked to this vibe will be permanently deleted. This is irreversible.'\n )}\n </p>\n </div>\n )}\n\n <label\n htmlFor=\"delete-vibe-confirm-name\"\n className=\"block text-xs font-medium text-text-secondary mb-1\"\n >\n {tr('vibeDelete.retypePrompt', 'Type the vibe name to confirm:')}{' '}\n <span className=\"font-mono text-text-primary break-all\">{vibe.name}</span>\n </label>\n <input\n id=\"delete-vibe-confirm-name\"\n ref={inputRef}\n type=\"text\"\n value={confirmName}\n disabled={deleting}\n onChange={(e) => setConfirmName(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleConfirm();\n }\n }}\n autoComplete=\"off\"\n spellCheck={false}\n aria-label={tr('vibeDelete.retypeAria', 'Vibe name confirmation')}\n placeholder={vibe.name}\n className=\"w-full text-sm px-3 py-2 mb-4 rounded-md bg-bg-surface border border-border-default text-text-primary placeholder:text-text-tertiary focus:outline-none focus:ring-2 focus:ring-status-error-text/40\"\n />\n\n <div className=\"flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={deleting}\n className=\"text-sm px-3 py-1.5 rounded-md border border-border-default text-text-primary hover:bg-action-ghost-bg-hover disabled:opacity-50\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleConfirm}\n disabled={!canDelete}\n className=\"inline-flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-md bg-status-error-bg text-status-error-text hover:opacity-80 disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {deleting ? <Loader2 className=\"size-4 animate-spin\" /> : <Trash2 className=\"size-4\" />}\n {deleteRelated\n ? tr('vibeDelete.confirmAll', 'Delete vibe & related')\n : tr('vibeDelete.confirm', 'Delete vibe')}\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -4,62 +4,62 @@ import { cardHoverVariants as n } from "../shared/cardMotion.js";
|
|
|
4
4
|
import { vibeTypeIcons as r } from "./icons.js";
|
|
5
5
|
import { useState as i } from "react";
|
|
6
6
|
import { useI18n as a } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
7
|
-
import { Archive as o, ExternalLink as s, FolderOpen as c, GitBranch as l, MoreVertical as u, Play as d, Settings as f, Tag as p } from "lucide-react";
|
|
8
|
-
import { AnimatePresence as
|
|
9
|
-
import { jsx as
|
|
7
|
+
import { Archive as o, ExternalLink as s, FolderOpen as c, GitBranch as l, MoreVertical as u, Play as d, Settings as f, Tag as p, Trash2 as m } from "lucide-react";
|
|
8
|
+
import { AnimatePresence as h, motion as g } from "framer-motion";
|
|
9
|
+
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
10
10
|
//#region src/components/vibes/VibeCard.tsx
|
|
11
|
-
var
|
|
12
|
-
let { t:
|
|
13
|
-
return /* @__PURE__ */
|
|
11
|
+
var y = ({ vibe: y, onOpen: b, onEdit: x, onArchive: S, onDelete: C, onActivate: w, onOpenInEditor: T, className: E = "", selected: D = !1, onToggleSelect: O }) => {
|
|
12
|
+
let { t: k } = a(), [A, j] = i(!1), M = r[y.type] || c;
|
|
13
|
+
return /* @__PURE__ */ v(g.div, {
|
|
14
14
|
initial: "rest",
|
|
15
15
|
animate: "rest",
|
|
16
16
|
whileHover: "hover",
|
|
17
17
|
whileTap: "tap",
|
|
18
18
|
variants: n,
|
|
19
|
-
className: `bg-bg-surface border rounded-lg p-4 hover:border-border-strong transition-colors cursor-pointer ${
|
|
20
|
-
onClick: () =>
|
|
19
|
+
className: `bg-bg-surface border rounded-lg p-4 hover:border-border-strong transition-colors cursor-pointer ${D ? "border-action-primary-bg ring-1 ring-action-primary-bg/25" : "border-border-default"} ${E}`,
|
|
20
|
+
onClick: () => b?.(y),
|
|
21
21
|
children: [
|
|
22
|
-
/* @__PURE__ */
|
|
22
|
+
/* @__PURE__ */ v("div", {
|
|
23
23
|
className: "flex items-start justify-between gap-2",
|
|
24
|
-
children: [/* @__PURE__ */
|
|
24
|
+
children: [/* @__PURE__ */ v("div", {
|
|
25
25
|
className: "flex items-start gap-2 sm:gap-3 min-w-0 flex-1",
|
|
26
26
|
children: [
|
|
27
|
-
|
|
27
|
+
O && /* @__PURE__ */ _("div", {
|
|
28
28
|
onClick: (e) => e.stopPropagation(),
|
|
29
|
-
children: /* @__PURE__ */
|
|
30
|
-
checked:
|
|
31
|
-
onChange:
|
|
32
|
-
ariaLabel: `Select vibe ${
|
|
29
|
+
children: /* @__PURE__ */ _(t, {
|
|
30
|
+
checked: D,
|
|
31
|
+
onChange: O,
|
|
32
|
+
ariaLabel: `Select vibe ${y.name}`,
|
|
33
33
|
className: "mt-2"
|
|
34
34
|
})
|
|
35
35
|
}),
|
|
36
|
-
/* @__PURE__ */
|
|
36
|
+
/* @__PURE__ */ _("div", {
|
|
37
37
|
className: "flex-shrink-0 size-8 sm:w-10 sm:h-10 rounded-lg bg-action-primary-bg/10 flex items-center justify-center",
|
|
38
|
-
children: /* @__PURE__ */
|
|
38
|
+
children: /* @__PURE__ */ _(M, { className: "size-4 sm:w-5 sm:h-5 text-action-primary-bg" })
|
|
39
39
|
}),
|
|
40
|
-
/* @__PURE__ */
|
|
40
|
+
/* @__PURE__ */ v("div", {
|
|
41
41
|
className: "min-w-0 flex-1",
|
|
42
|
-
children: [/* @__PURE__ */
|
|
42
|
+
children: [/* @__PURE__ */ _("h3", {
|
|
43
43
|
className: "font-semibold text-text-primary truncate text-sm sm:text-base",
|
|
44
|
-
children:
|
|
45
|
-
}),
|
|
44
|
+
children: y.name
|
|
45
|
+
}), y.description && /* @__PURE__ */ _("p", {
|
|
46
46
|
className: "text-xs sm:text-sm text-text-secondary truncate",
|
|
47
|
-
children:
|
|
47
|
+
children: y.description
|
|
48
48
|
})]
|
|
49
49
|
})
|
|
50
50
|
]
|
|
51
|
-
}), /* @__PURE__ */
|
|
51
|
+
}), /* @__PURE__ */ v("div", {
|
|
52
52
|
className: "flex items-center gap-1 sm:gap-2 flex-shrink-0",
|
|
53
|
-
children: [/* @__PURE__ */
|
|
53
|
+
children: [/* @__PURE__ */ _(e, { status: y.status }), (x || S || C || w || T) && /* @__PURE__ */ v("div", {
|
|
54
54
|
className: "relative",
|
|
55
|
-
children: [/* @__PURE__ */
|
|
55
|
+
children: [/* @__PURE__ */ _("button", {
|
|
56
56
|
type: "button",
|
|
57
57
|
onClick: (e) => {
|
|
58
|
-
e.stopPropagation(),
|
|
58
|
+
e.stopPropagation(), j(!A);
|
|
59
59
|
},
|
|
60
60
|
className: "p-1 sm:p-1.5 rounded hover:bg-bg-sunken transition-colors",
|
|
61
|
-
children: /* @__PURE__ */
|
|
62
|
-
}), /* @__PURE__ */
|
|
61
|
+
children: /* @__PURE__ */ _(u, { className: "size-4 text-text-secondary" })
|
|
62
|
+
}), /* @__PURE__ */ _(h, { children: A && /* @__PURE__ */ v(g.div, {
|
|
63
63
|
initial: {
|
|
64
64
|
opacity: 0,
|
|
65
65
|
scale: .95,
|
|
@@ -78,79 +78,87 @@ var v = ({ vibe: v, onOpen: y, onEdit: b, onArchive: x, onActivate: S, onOpenInE
|
|
|
78
78
|
transition: { duration: .15 },
|
|
79
79
|
className: "absolute right-0 mt-1 w-40 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10",
|
|
80
80
|
children: [
|
|
81
|
-
|
|
81
|
+
w && y.status !== "ACTIVE" && /* @__PURE__ */ v(g.button, {
|
|
82
82
|
whileTap: { scale: .97 },
|
|
83
83
|
onClick: (e) => {
|
|
84
|
-
e.stopPropagation(),
|
|
84
|
+
e.stopPropagation(), w(y), j(!1);
|
|
85
85
|
},
|
|
86
86
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg",
|
|
87
|
-
children: [/* @__PURE__ */
|
|
87
|
+
children: [/* @__PURE__ */ _(d, { className: "size-4" }), "Activate"]
|
|
88
88
|
}),
|
|
89
|
-
|
|
89
|
+
x && /* @__PURE__ */ v(g.button, {
|
|
90
90
|
whileTap: { scale: .97 },
|
|
91
91
|
onClick: (e) => {
|
|
92
|
-
e.stopPropagation(),
|
|
92
|
+
e.stopPropagation(), x(y), j(!1);
|
|
93
93
|
},
|
|
94
94
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
|
|
95
|
-
children: [/* @__PURE__ */
|
|
95
|
+
children: [/* @__PURE__ */ _(f, { className: "size-4" }), "Edit"]
|
|
96
96
|
}),
|
|
97
|
-
|
|
97
|
+
T && y.path && /* @__PURE__ */ v(g.button, {
|
|
98
98
|
whileTap: { scale: .97 },
|
|
99
99
|
onClick: (e) => {
|
|
100
|
-
e.stopPropagation(),
|
|
100
|
+
e.stopPropagation(), T(y), j(!1);
|
|
101
101
|
},
|
|
102
102
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken",
|
|
103
|
-
children: [/* @__PURE__ */
|
|
103
|
+
children: [/* @__PURE__ */ _(s, { className: "size-4" }), "Open in VS Code"]
|
|
104
104
|
}),
|
|
105
|
-
|
|
105
|
+
S && y.status !== "ARCHIVED" && /* @__PURE__ */ v(g.button, {
|
|
106
106
|
whileTap: { scale: .97 },
|
|
107
107
|
onClick: (e) => {
|
|
108
|
-
e.stopPropagation(),
|
|
108
|
+
e.stopPropagation(), S(y), j(!1);
|
|
109
109
|
},
|
|
110
110
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-b-lg",
|
|
111
|
-
children: [/* @__PURE__ */
|
|
111
|
+
children: [/* @__PURE__ */ _(o, { className: "size-4" }), "Archive"]
|
|
112
|
+
}),
|
|
113
|
+
C && /* @__PURE__ */ v(g.button, {
|
|
114
|
+
whileTap: { scale: .97 },
|
|
115
|
+
onClick: (e) => {
|
|
116
|
+
e.stopPropagation(), C(y), j(!1);
|
|
117
|
+
},
|
|
118
|
+
className: "flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg",
|
|
119
|
+
children: [/* @__PURE__ */ _(m, { className: "size-4" }), "Delete"]
|
|
112
120
|
})
|
|
113
121
|
]
|
|
114
122
|
}) })]
|
|
115
123
|
})]
|
|
116
124
|
})]
|
|
117
125
|
}),
|
|
118
|
-
|
|
126
|
+
y.description && /* @__PURE__ */ _("p", {
|
|
119
127
|
className: "mt-2 text-sm text-text-secondary line-clamp-2",
|
|
120
|
-
children:
|
|
128
|
+
children: y.description
|
|
121
129
|
}),
|
|
122
|
-
|
|
130
|
+
y.path && /* @__PURE__ */ _("div", {
|
|
123
131
|
className: "mt-3 text-xs text-text-secondary font-mono bg-bg-sunken p-2 rounded truncate",
|
|
124
|
-
children:
|
|
132
|
+
children: y.path
|
|
125
133
|
}),
|
|
126
|
-
|
|
134
|
+
y.gitBranch && /* @__PURE__ */ v("div", {
|
|
127
135
|
className: "mt-2 flex items-center gap-2 text-sm text-text-secondary",
|
|
128
136
|
children: [
|
|
129
|
-
/* @__PURE__ */
|
|
130
|
-
/* @__PURE__ */
|
|
137
|
+
/* @__PURE__ */ _(l, { className: "size-4" }),
|
|
138
|
+
/* @__PURE__ */ _("span", {
|
|
131
139
|
className: "truncate",
|
|
132
|
-
children:
|
|
140
|
+
children: y.gitBranch
|
|
133
141
|
}),
|
|
134
|
-
|
|
142
|
+
y.gitStatus && y.gitStatus !== "clean" && /* @__PURE__ */ _("span", {
|
|
135
143
|
className: "px-1.5 py-0.5 text-xs bg-status-warning-bg text-status-warning-text rounded",
|
|
136
|
-
children:
|
|
144
|
+
children: y.gitStatus
|
|
137
145
|
})
|
|
138
146
|
]
|
|
139
147
|
}),
|
|
140
|
-
|
|
148
|
+
y.tags && y.tags.length > 0 && /* @__PURE__ */ v("div", {
|
|
141
149
|
className: "mt-3 flex flex-wrap gap-1",
|
|
142
|
-
children: [
|
|
150
|
+
children: [y.tags.slice(0, 3).map((e) => /* @__PURE__ */ v("span", {
|
|
143
151
|
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded",
|
|
144
|
-
children: [/* @__PURE__ */
|
|
145
|
-
}, e)),
|
|
152
|
+
children: [/* @__PURE__ */ _(p, { className: "size-3" }), e]
|
|
153
|
+
}, e)), y.tags.length > 3 && /* @__PURE__ */ v("span", {
|
|
146
154
|
className: "px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded",
|
|
147
|
-
children: ["+",
|
|
155
|
+
children: ["+", y.tags.length - 3]
|
|
148
156
|
})]
|
|
149
157
|
})
|
|
150
158
|
]
|
|
151
159
|
});
|
|
152
160
|
};
|
|
153
161
|
//#endregion
|
|
154
|
-
export {
|
|
162
|
+
export { y as VibeCard };
|
|
155
163
|
|
|
156
164
|
//# sourceMappingURL=VibeCard.js.map
|