@burdenoff/microfe-subscriberbot 2026.731.1 → 2026.827.1
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/subscriberbot/SubscriberBotRoutes.js +1 -1
- package/dist/subscriberbot/SubscriberBotRoutes.js.map +1 -1
- package/dist/subscriberbot/components/viz.js +51 -27
- package/dist/subscriberbot/components/viz.js.map +1 -1
- package/dist/subscriberbot/context/SubscriberBotContext.js +1 -1
- package/dist/subscriberbot/context/SubscriberBotContext.js.map +1 -1
- package/dist/subscriberbot/hooks/index.d.ts +1 -1
- package/dist/subscriberbot/hooks/index.d.ts.map +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts +12 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts.map +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.js +82 -56
- package/dist/subscriberbot/hooks/useSubscriberBotApi.js.map +1 -1
- package/dist/subscriberbot/pages/AssetVaultPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/AssetVaultPage.js +134 -78
- package/dist/subscriberbot/pages/AssetVaultPage.js.map +1 -1
- package/dist/subscriberbot/pages/InvoicesPage.d.ts +3 -2
- package/dist/subscriberbot/pages/InvoicesPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/InvoicesPage.js +43 -31
- package/dist/subscriberbot/pages/InvoicesPage.js.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentDetailPage.d.ts +3 -1
- package/dist/subscriberbot/pages/agents/AgentDetailPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentDetailPage.js +195 -159
- package/dist/subscriberbot/pages/agents/AgentDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.d.ts +4 -2
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.js +368 -266
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -1
- package/dist/subscriberbot/types/index.d.ts +37 -0
- package/dist/subscriberbot/types/index.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderDetailPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/providers/ProviderDetailPage.tsx"],"sourcesContent":["/**\n * Provider detail\n * @module subscriberbot/pages/providers\n *\n * A provider profile from the registry: category, status, region, scores,\n * tags and its published plans. Reads `subProvider(id)`; writes\n * `subUpdateProvider`, `subArchiveProvider`, `subCreatePlan`.\n */\n\nimport { useState } from 'react';\nimport {\n ArrowLeft,\n Building2,\n Globe,\n ShieldCheck,\n TrendingDown,\n Tag,\n Pencil,\n Plus,\n Archive,\n Check,\n Sparkles,\n Layers,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Modal } from '../../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { Gauge, MiniProgress } from '../../components';\nimport { useParams } from 'react-router-dom';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport {\n useProvider,\n useUpdateProvider,\n useArchiveProvider,\n useCreatePlan,\n} from '../../hooks/useSubscriberBotApi';\nimport { formatCurrency, formatEnumLabel, formatIntervalSuffix } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type {\n SubBillingInterval,\n SubPlan,\n SubProvider,\n SubProviderCategory,\n SubProviderStatus,\n SubUpdateProviderInput,\n} from '../../types';\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst CATEGORIES: SubProviderCategory[] = [\n 'SAAS',\n 'AI_SERVICE',\n 'STREAMING',\n 'NEWSLETTER',\n 'NEWS_DIGEST',\n 'MEMBERSHIP',\n 'COMMUNITY',\n 'EDUCATION',\n 'FINANCIAL',\n 'INSURANCE',\n 'TELECOM',\n 'UTILITY',\n 'ASSOCIATION',\n 'LICENSE',\n 'CONTRACT',\n 'OTHER',\n];\n\nconst STATUSES: SubProviderStatus[] = ['UNVERIFIED', 'VERIFIED', 'CLAIMED', 'DEPRECATED'];\n\nconst INTERVALS: SubBillingInterval[] = [\n 'MONTHLY',\n 'YEARLY',\n 'QUARTERLY',\n 'WEEKLY',\n 'DAILY',\n 'USAGE_BASED',\n 'LIFETIME',\n 'FREE',\n];\n\n/** Vendor-health 0–100 → label + semantic chip classes. Higher is healthier. */\nfunction healthMeta(score: number): { label: string; cls: string } {\n if (score >= 70)\n return { label: 'Healthy', cls: 'bg-status-success-bg-subtle text-status-success-text' };\n if (score >= 45)\n return { label: 'Fair', cls: 'bg-status-warning-bg-subtle text-status-warning-text' };\n return { label: 'At risk', cls: 'bg-status-error-bg-subtle text-status-error-text' };\n}\n\n/** Churn-risk 0–100 → label + tone. Higher is worse. */\nfunction churnMeta(score: number): {\n label: string;\n chip: string;\n tone: 'positive' | 'warning' | 'negative';\n} {\n if (score >= 67)\n return {\n label: 'High',\n chip: 'bg-status-error-bg-subtle text-status-error-text',\n tone: 'negative',\n };\n if (score >= 34)\n return {\n label: 'Moderate',\n chip: 'bg-status-warning-bg-subtle text-status-warning-text',\n tone: 'warning',\n };\n return {\n label: 'Low',\n chip: 'bg-status-success-bg-subtle text-status-success-text',\n tone: 'positive',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Edit provider modal → subUpdateProvider\n// ---------------------------------------------------------------------------\n\nfunction ProviderEditModal({ provider, onClose }: { provider: SubProvider; onClose: () => void }) {\n const update = useUpdateProvider();\n const [name, setName] = useState(provider.name);\n const [category, setCategory] = useState<SubProviderCategory>(provider.category);\n const [status, setStatus] = useState<SubProviderStatus>(provider.status);\n const [description, setDescription] = useState(provider.description ?? '');\n const [industry, setIndustry] = useState(provider.industry ?? '');\n const [region, setRegion] = useState(provider.region ?? '');\n const [language, setLanguage] = useState(provider.language ?? '');\n const [websiteUrl, setWebsiteUrl] = useState(provider.websiteUrl ?? '');\n const [churnRiskScore, setChurnRiskScore] = useState(\n provider.churnRiskScore != null ? String(provider.churnRiskScore) : ''\n );\n const [vendorHealthScore, setVendorHealthScore] = useState(\n provider.vendorHealthScore != null ? String(provider.vendorHealthScore) : ''\n );\n const [tags, setTags] = useState(provider.tags.join(', '));\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('A provider name is required.');\n return;\n }\n const parsedTags = tags\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n const input: SubUpdateProviderInput = {\n name: name.trim(),\n category,\n status,\n description: description.trim(),\n tags: parsedTags,\n ...(industry.trim() ? { industry: industry.trim() } : {}),\n ...(region.trim() ? { region: region.trim() } : {}),\n ...(language.trim() ? { language: language.trim() } : {}),\n ...(websiteUrl.trim() ? { websiteUrl: websiteUrl.trim() } : {}),\n ...(churnRiskScore.trim() ? { churnRiskScore: Number(churnRiskScore) } : {}),\n ...(vendorHealthScore.trim() ? { vendorHealthScore: Number(vendorHealthScore) } : {}),\n };\n update.mutate(\n { id: provider.id, input },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not update provider.'),\n }\n );\n };\n\n return (\n <Modal title=\"Edit provider\" onClose={onClose} maxWidthClassName=\"max-w-2xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"prov-edit-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"prov-edit-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className={inputCls}\n required\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"prov-edit-category\" className={labelCls}>\n Category\n </label>\n <select\n id=\"prov-edit-category\"\n value={category}\n onChange={(e) => setCategory(e.target.value as SubProviderCategory)}\n className={inputCls}\n >\n {CATEGORIES.map((c) => (\n <option key={c} value={c}>\n {c.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"prov-edit-status\" className={labelCls}>\n Status\n </label>\n <select\n id=\"prov-edit-status\"\n value={status}\n onChange={(e) => setStatus(e.target.value as SubProviderStatus)}\n className={inputCls}\n >\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"prov-edit-industry\" className={labelCls}>\n Industry\n </label>\n <input\n id=\"prov-edit-industry\"\n type=\"text\"\n value={industry}\n onChange={(e) => setIndustry(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-region\" className={labelCls}>\n Region\n </label>\n <input\n id=\"prov-edit-region\"\n type=\"text\"\n value={region}\n onChange={(e) => setRegion(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-language\" className={labelCls}>\n Language\n </label>\n <input\n id=\"prov-edit-language\"\n type=\"text\"\n value={language}\n onChange={(e) => setLanguage(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"prov-edit-website\" className={labelCls}>\n Website\n </label>\n <input\n id=\"prov-edit-website\"\n type=\"url\"\n value={websiteUrl}\n onChange={(e) => setWebsiteUrl(e.target.value)}\n placeholder=\"https://…\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"prov-edit-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={3}\n className={inputCls}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"prov-edit-churn\" className={labelCls}>\n Churn risk (0–100)\n </label>\n <input\n id=\"prov-edit-churn\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n value={churnRiskScore}\n onChange={(e) => setChurnRiskScore(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-health\" className={labelCls}>\n Vendor health (0–100)\n </label>\n <input\n id=\"prov-edit-health\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n value={vendorHealthScore}\n onChange={(e) => setVendorHealthScore(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"prov-edit-tags\" className={labelCls}>\n Tags <span className=\"text-text-secondary\">(comma-separated)</span>\n </label>\n <input\n id=\"prov-edit-tags\"\n type=\"text\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n className={inputCls}\n />\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={update.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {update.isPending ? 'Saving…' : 'Save changes'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add plan modal → subCreatePlan\n// ---------------------------------------------------------------------------\n\nfunction AddPlanModal({ providerId, onClose }: { providerId: string; onClose: () => void }) {\n const create = useCreatePlan();\n const [name, setName] = useState('');\n const [amount, setAmount] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [interval, setInterval] = useState<SubBillingInterval>('MONTHLY');\n const [description, setDescription] = useState('');\n const [features, setFeatures] = useState('');\n const [isActive, setIsActive] = useState(true);\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('A plan name is required.');\n return;\n }\n const parsedFeatures = features\n .split('\\n')\n .map((f) => f.trim())\n .filter(Boolean);\n create.mutate(\n {\n providerId,\n name: name.trim(),\n interval,\n currency: currency.trim() || 'USD',\n ...(amount.trim() ? { amount: Number(amount) } : {}),\n ...(description.trim() ? { description: description.trim() } : {}),\n ...(parsedFeatures.length ? { features: parsedFeatures } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) => setFormError(err instanceof Error ? err.message : 'Could not add plan.'),\n }\n );\n };\n\n return (\n <Modal title=\"Add plan\" onClose={onClose} maxWidthClassName=\"max-w-xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"plan-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"plan-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Pro, Team, Enterprise\"\n className={inputCls}\n required\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"plan-amount\" className={labelCls}>\n Amount\n </label>\n <input\n id=\"plan-amount\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n placeholder=\"0.00\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-currency\" className={labelCls}>\n Currency\n </label>\n <input\n id=\"plan-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-interval\" className={labelCls}>\n Interval\n </label>\n <select\n id=\"plan-interval\"\n value={interval}\n onChange={(e) => setInterval(e.target.value as SubBillingInterval)}\n className={inputCls}\n >\n {INTERVALS.map((i) => (\n <option key={i} value={i}>\n {i.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label htmlFor=\"plan-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"plan-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-features\" className={labelCls}>\n Features <span className=\"text-text-secondary\">(one per line)</span>\n </label>\n <textarea\n id=\"plan-features\"\n value={features}\n onChange={(e) => setFeatures(e.target.value)}\n rows={3}\n placeholder={'Unlimited projects\\nPriority support\\nSSO'}\n className={inputCls}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Active\n </label>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add plan'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Archive confirmation modal → subArchiveProvider\n// ---------------------------------------------------------------------------\n\nfunction ArchiveProviderModal({\n provider,\n onClose,\n}: {\n provider: SubProvider;\n onClose: () => void;\n}) {\n const { navigateTo } = useSubscriberBot();\n const archive = useArchiveProvider();\n const [error, setError] = useState<string | null>(null);\n\n const handleArchive = () => {\n setError(null);\n archive.mutate(provider.id, {\n onSuccess: () => navigateTo('/providers'),\n onError: (err) =>\n setError(err instanceof Error ? err.message : 'Could not archive this provider.'),\n });\n };\n\n return (\n <Modal title=\"Archive provider\" onClose={onClose}>\n <div className=\"space-y-4\">\n {error && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n Archive <span className=\"font-medium text-text-primary\">{provider.name}</span>? It will be\n removed from the active registry. This can be reversed by an administrator.\n </p>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleArchive}\n disabled={archive.isPending}\n className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm font-medium text-status-error-text hover:opacity-90 disabled:opacity-50\"\n >\n {archive.isPending ? 'Archiving…' : 'Archive provider'}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n\nexport function ProviderDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useSubscriberBot();\n const { data: provider, isLoading, isError, refetch } = useProvider(id);\n const [showEdit, setShowEdit] = useState(false);\n const [showAddPlan, setShowAddPlan] = useState(false);\n const [showArchive, setShowArchive] = useState(false);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Provider\">\n <LoadingView label=\"Loading provider…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Provider\">\n <ErrorView message=\"Could not load this provider.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!provider) {\n return (\n <PageLayout title=\"Provider\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"Provider not found\"\n description=\"It may have been removed, or you may not have access to it.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Back to providers\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n const p = provider;\n // Only render the website as a link when it is a safe http(s) URL — websiteUrl\n // is user-controlled, so guard against javascript:/data: schemes and add\n // rel=\"noopener noreferrer\" to the target=\"_blank\" anchor.\n const safeWebsiteUrl = p.websiteUrl && /^https?:\\/\\//i.test(p.websiteUrl) ? p.websiteUrl : null;\n\n // Highlight the cheapest active priced plan as \"Best value\" (derived from real\n // plan data — guard for the no-plans / all-free case).\n const bestValuePlanId =\n p.plans && p.plans.length > 1\n ? (p.plans\n .filter((pl) => pl.isActive && pl.amount > 0)\n .reduce<SubPlan | null>(\n (min, pl) => (min === null || pl.amount < min.amount ? pl : min),\n null\n )?.id ?? null)\n : null;\n\n return (\n <PageLayout\n title={p.name}\n description={formatEnumLabel(p.category)}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setShowEdit(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n <button\n type=\"button\"\n onClick={() => setShowArchive(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-status-error-text/30 px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle\"\n >\n <Archive className=\"h-4 w-4\" /> Archive\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n </div>\n }\n >\n {/* Identity row */}\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"flex size-11 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <Building2 className=\"h-5 w-5\" />\n </span>\n <StatusBadge status={p.status} />\n {p.industry && <span className=\"text-sm text-text-secondary\">{p.industry}</span>}\n {p.region && <span className=\"text-sm text-text-secondary\">· {p.region}</span>}\n {safeWebsiteUrl && (\n <a\n href={safeWebsiteUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-sm text-text-link hover:underline\"\n >\n <Globe className=\"h-3.5 w-3.5\" /> Website\n </a>\n )}\n </div>\n\n {p.description && (\n <p className=\"mt-4 max-w-3xl text-sm leading-relaxed text-text-secondary\">\n {p.description}\n </p>\n )}\n\n {p.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap items-center gap-2\">\n <Tag className=\"h-3.5 w-3.5 text-text-secondary\" />\n {p.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Plans — tier cards */}\n <div className=\"lg:col-span-2\">\n <Section\n title=\"Plans\"\n description=\"Pricing this provider publishes\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddPlan(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add plan\n </button>\n }\n >\n {!p.plans || p.plans.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No published plans\"\n description=\"When this provider publishes plans, they'll appear here.\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddPlan(true)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Add plan\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {p.plans.map((plan) => {\n const isBestValue = plan.id === bestValuePlanId && p.plans!.length > 1;\n return (\n <GlassCard\n key={plan.id}\n treatment={isBestValue ? 'insight' : 'metric'}\n glow={isBestValue}\n className=\"flex flex-col p-5\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"text-sm font-semibold text-text-primary\">{plan.name}</p>\n {isBestValue ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-[11px] font-medium text-status-success-text\">\n <Sparkles className=\"h-3 w-3\" /> Best value\n </span>\n ) : (\n <StatusBadge status={plan.isActive ? 'ACTIVE' : 'DISABLED'} />\n )}\n </div>\n <p className=\"mt-3 text-2xl font-bold tabular-nums text-text-primary\">\n {formatCurrency(plan.amount, plan.currency)}\n <span className=\"text-sm font-normal text-text-secondary\">\n {formatIntervalSuffix(plan.interval)}\n </span>\n </p>\n {plan.description && (\n <p className=\"mt-2 text-xs leading-relaxed text-text-secondary\">\n {plan.description}\n </p>\n )}\n {plan.features.length > 0 && (\n <ul className=\"mt-4 space-y-2 border-t border-border-subtle pt-4\">\n {plan.features.slice(0, 6).map((f) => (\n <li\n key={f}\n className=\"flex items-start gap-2 text-xs text-text-secondary\"\n >\n <Check className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-status-success-text\" />\n <span>{f}</span>\n </li>\n ))}\n {plan.features.length > 6 && (\n <li className=\"pl-6 text-[11px] text-text-secondary\">\n +{plan.features.length - 6} more\n </li>\n )}\n </ul>\n )}\n </GlassCard>\n );\n })}\n </div>\n )}\n </Section>\n </div>\n\n {/* Sidebar — vendor health, churn risk, at-a-glance */}\n <div className=\"space-y-4\">\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center justify-between\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <ShieldCheck className=\"h-4 w-4 text-text-secondary\" /> Vendor health\n </span>\n {p.vendorHealthScore != null && (\n <span\n className={cn(\n 'rounded-full px-2 py-0.5 text-[11px] font-medium',\n healthMeta(p.vendorHealthScore).cls\n )}\n >\n {healthMeta(p.vendorHealthScore).label}\n </span>\n )}\n </div>\n {p.vendorHealthScore != null ? (\n <div className=\"mt-2 flex flex-col items-center\">\n <Gauge value={p.vendorHealthScore} max={100} size={168} label=\"out of 100\" />\n </div>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">No health score yet.</p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center justify-between\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <TrendingDown className=\"h-4 w-4 text-text-secondary\" /> Churn risk\n </span>\n {p.churnRiskScore != null && (\n <span\n className={cn(\n 'rounded-full px-2 py-0.5 text-[11px] font-medium',\n churnMeta(p.churnRiskScore).chip\n )}\n >\n {churnMeta(p.churnRiskScore).label}\n </span>\n )}\n </div>\n {p.churnRiskScore != null ? (\n <>\n <div className=\"mt-3 flex items-baseline justify-between\">\n <span className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {p.churnRiskScore}\n </span>\n <span className=\"text-xs text-text-secondary\">/ 100</span>\n </div>\n <MiniProgress\n value={p.churnRiskScore}\n tone={churnMeta(p.churnRiskScore).tone}\n className=\"mt-2\"\n />\n </>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">No churn signal yet.</p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Layers className=\"h-4 w-4 text-text-secondary\" /> At a glance\n </span>\n <dl className=\"mt-3 space-y-2.5 text-sm\">\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Category</dt>\n <dd className=\"font-medium text-text-primary\">{formatEnumLabel(p.category)}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Industry</dt>\n <dd className=\"font-medium text-text-primary\">{p.industry ?? '—'}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Region</dt>\n <dd className=\"font-medium text-text-primary\">{p.region ?? '—'}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Language</dt>\n <dd className=\"font-medium text-text-primary\">{p.language ?? '—'}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Published plans</dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">\n {p.plans?.length ?? 0}\n </dd>\n </div>\n </dl>\n </GlassCard>\n </div>\n </div>\n\n {showEdit && <ProviderEditModal provider={p} onClose={() => setShowEdit(false)} />}\n {showAddPlan && <AddPlanModal providerId={p.id} onClose={() => setShowAddPlan(false)} />}\n {showArchive && <ArchiveProviderModal provider={p} onClose={() => setShowArchive(false)} />}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProviderDetailPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/providers/ProviderDetailPage.tsx"],"sourcesContent":["/**\n * Provider detail\n * @module subscriberbot/pages/providers\n *\n * A provider profile from the registry: category, status, region, scores,\n * tags and its published plans. Reads `subProvider(id)` and\n * `subListBenchmarkDataPoints` (the observed price points behind the \"Price\n * history\" card); writes `subUpdateProvider`, `subArchiveProvider`,\n * `subCreatePlan`.\n */\n\nimport { useMemo, useState } from 'react';\nimport {\n ArrowLeft,\n Building2,\n Globe,\n ShieldCheck,\n TrendingDown,\n Tag,\n Pencil,\n Plus,\n Archive,\n Check,\n Sparkles,\n Layers,\n LineChart,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Modal } from '../../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { Gauge, MiniProgress, Sparkline } from '../../components';\nimport { useParams } from 'react-router';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport {\n useProvider,\n useUpdateProvider,\n useArchiveProvider,\n useCreatePlan,\n useBenchmarkDataPoints,\n} from '../../hooks/useSubscriberBotApi';\nimport {\n formatCurrency,\n formatDate,\n formatEnumLabel,\n formatIntervalSuffix,\n} from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type {\n SubBenchmarkDataPoint,\n SubBillingInterval,\n SubPlan,\n SubProvider,\n SubProviderCategory,\n SubProviderStatus,\n SubUpdateProviderInput,\n} from '../../types';\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst CATEGORIES: SubProviderCategory[] = [\n 'SAAS',\n 'AI_SERVICE',\n 'STREAMING',\n 'NEWSLETTER',\n 'NEWS_DIGEST',\n 'MEMBERSHIP',\n 'COMMUNITY',\n 'EDUCATION',\n 'FINANCIAL',\n 'INSURANCE',\n 'TELECOM',\n 'UTILITY',\n 'ASSOCIATION',\n 'LICENSE',\n 'CONTRACT',\n 'OTHER',\n];\n\nconst STATUSES: SubProviderStatus[] = ['UNVERIFIED', 'VERIFIED', 'CLAIMED', 'DEPRECATED'];\n\nconst INTERVALS: SubBillingInterval[] = [\n 'MONTHLY',\n 'YEARLY',\n 'QUARTERLY',\n 'WEEKLY',\n 'DAILY',\n 'USAGE_BASED',\n 'LIFETIME',\n 'FREE',\n];\n\n/** Vendor-health 0–100 → label + semantic chip classes. Higher is healthier. */\nfunction healthMeta(score: number): { label: string; cls: string } {\n if (score >= 70)\n return { label: 'Healthy', cls: 'bg-status-success-bg-subtle text-status-success-text' };\n if (score >= 45)\n return { label: 'Fair', cls: 'bg-status-warning-bg-subtle text-status-warning-text' };\n return { label: 'At risk', cls: 'bg-status-error-bg-subtle text-status-error-text' };\n}\n\n/** Churn-risk 0–100 → label + tone. Higher is worse. */\nfunction churnMeta(score: number): {\n label: string;\n chip: string;\n tone: 'positive' | 'warning' | 'negative';\n} {\n if (score >= 67)\n return {\n label: 'High',\n chip: 'bg-status-error-bg-subtle text-status-error-text',\n tone: 'negative',\n };\n if (score >= 34)\n return {\n label: 'Moderate',\n chip: 'bg-status-warning-bg-subtle text-status-warning-text',\n tone: 'warning',\n };\n return {\n label: 'Low',\n chip: 'bg-status-success-bg-subtle text-status-success-text',\n tone: 'positive',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Edit provider modal → subUpdateProvider\n// ---------------------------------------------------------------------------\n\nfunction ProviderEditModal({ provider, onClose }: { provider: SubProvider; onClose: () => void }) {\n const update = useUpdateProvider();\n const [name, setName] = useState(provider.name);\n const [category, setCategory] = useState<SubProviderCategory>(provider.category);\n const [status, setStatus] = useState<SubProviderStatus>(provider.status);\n const [description, setDescription] = useState(provider.description ?? '');\n const [industry, setIndustry] = useState(provider.industry ?? '');\n const [region, setRegion] = useState(provider.region ?? '');\n const [language, setLanguage] = useState(provider.language ?? '');\n const [websiteUrl, setWebsiteUrl] = useState(provider.websiteUrl ?? '');\n const [churnRiskScore, setChurnRiskScore] = useState(\n provider.churnRiskScore != null ? String(provider.churnRiskScore) : ''\n );\n const [vendorHealthScore, setVendorHealthScore] = useState(\n provider.vendorHealthScore != null ? String(provider.vendorHealthScore) : ''\n );\n const [tags, setTags] = useState(provider.tags.join(', '));\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('A provider name is required.');\n return;\n }\n const parsedTags = tags\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n const input: SubUpdateProviderInput = {\n name: name.trim(),\n category,\n status,\n description: description.trim(),\n tags: parsedTags,\n ...(industry.trim() ? { industry: industry.trim() } : {}),\n ...(region.trim() ? { region: region.trim() } : {}),\n ...(language.trim() ? { language: language.trim() } : {}),\n ...(websiteUrl.trim() ? { websiteUrl: websiteUrl.trim() } : {}),\n ...(churnRiskScore.trim() ? { churnRiskScore: Number(churnRiskScore) } : {}),\n ...(vendorHealthScore.trim() ? { vendorHealthScore: Number(vendorHealthScore) } : {}),\n };\n update.mutate(\n { id: provider.id, input },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not update provider.'),\n }\n );\n };\n\n return (\n <Modal title=\"Edit provider\" onClose={onClose} maxWidthClassName=\"max-w-2xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"prov-edit-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"prov-edit-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className={inputCls}\n required\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"prov-edit-category\" className={labelCls}>\n Category\n </label>\n <select\n id=\"prov-edit-category\"\n value={category}\n onChange={(e) => setCategory(e.target.value as SubProviderCategory)}\n className={inputCls}\n >\n {CATEGORIES.map((c) => (\n <option key={c} value={c}>\n {c.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"prov-edit-status\" className={labelCls}>\n Status\n </label>\n <select\n id=\"prov-edit-status\"\n value={status}\n onChange={(e) => setStatus(e.target.value as SubProviderStatus)}\n className={inputCls}\n >\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"prov-edit-industry\" className={labelCls}>\n Industry\n </label>\n <input\n id=\"prov-edit-industry\"\n type=\"text\"\n value={industry}\n onChange={(e) => setIndustry(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-region\" className={labelCls}>\n Region\n </label>\n <input\n id=\"prov-edit-region\"\n type=\"text\"\n value={region}\n onChange={(e) => setRegion(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-language\" className={labelCls}>\n Language\n </label>\n <input\n id=\"prov-edit-language\"\n type=\"text\"\n value={language}\n onChange={(e) => setLanguage(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"prov-edit-website\" className={labelCls}>\n Website\n </label>\n <input\n id=\"prov-edit-website\"\n type=\"url\"\n value={websiteUrl}\n onChange={(e) => setWebsiteUrl(e.target.value)}\n placeholder=\"https://…\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"prov-edit-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={3}\n className={inputCls}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"prov-edit-churn\" className={labelCls}>\n Churn risk (0–100)\n </label>\n <input\n id=\"prov-edit-churn\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n value={churnRiskScore}\n onChange={(e) => setChurnRiskScore(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-health\" className={labelCls}>\n Vendor health (0–100)\n </label>\n <input\n id=\"prov-edit-health\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n value={vendorHealthScore}\n onChange={(e) => setVendorHealthScore(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"prov-edit-tags\" className={labelCls}>\n Tags <span className=\"text-text-secondary\">(comma-separated)</span>\n </label>\n <input\n id=\"prov-edit-tags\"\n type=\"text\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n className={inputCls}\n />\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={update.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {update.isPending ? 'Saving…' : 'Save changes'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add plan modal → subCreatePlan\n// ---------------------------------------------------------------------------\n\nfunction AddPlanModal({ providerId, onClose }: { providerId: string; onClose: () => void }) {\n const create = useCreatePlan();\n const [name, setName] = useState('');\n const [amount, setAmount] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [interval, setInterval] = useState<SubBillingInterval>('MONTHLY');\n const [description, setDescription] = useState('');\n const [features, setFeatures] = useState('');\n const [isActive, setIsActive] = useState(true);\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('A plan name is required.');\n return;\n }\n const parsedFeatures = features\n .split('\\n')\n .map((f) => f.trim())\n .filter(Boolean);\n create.mutate(\n {\n providerId,\n name: name.trim(),\n interval,\n currency: currency.trim() || 'USD',\n ...(amount.trim() ? { amount: Number(amount) } : {}),\n ...(description.trim() ? { description: description.trim() } : {}),\n ...(parsedFeatures.length ? { features: parsedFeatures } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) => setFormError(err instanceof Error ? err.message : 'Could not add plan.'),\n }\n );\n };\n\n return (\n <Modal title=\"Add plan\" onClose={onClose} maxWidthClassName=\"max-w-xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"plan-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"plan-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Pro, Team, Enterprise\"\n className={inputCls}\n required\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"plan-amount\" className={labelCls}>\n Amount\n </label>\n <input\n id=\"plan-amount\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n placeholder=\"0.00\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-currency\" className={labelCls}>\n Currency\n </label>\n <input\n id=\"plan-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-interval\" className={labelCls}>\n Interval\n </label>\n <select\n id=\"plan-interval\"\n value={interval}\n onChange={(e) => setInterval(e.target.value as SubBillingInterval)}\n className={inputCls}\n >\n {INTERVALS.map((i) => (\n <option key={i} value={i}>\n {i.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label htmlFor=\"plan-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"plan-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-features\" className={labelCls}>\n Features <span className=\"text-text-secondary\">(one per line)</span>\n </label>\n <textarea\n id=\"plan-features\"\n value={features}\n onChange={(e) => setFeatures(e.target.value)}\n rows={3}\n placeholder={'Unlimited projects\\nPriority support\\nSSO'}\n className={inputCls}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Active\n </label>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add plan'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Archive confirmation modal → subArchiveProvider\n// ---------------------------------------------------------------------------\n\nfunction ArchiveProviderModal({\n provider,\n onClose,\n}: {\n provider: SubProvider;\n onClose: () => void;\n}) {\n const { navigateTo } = useSubscriberBot();\n const archive = useArchiveProvider();\n const [error, setError] = useState<string | null>(null);\n\n const handleArchive = () => {\n setError(null);\n archive.mutate(provider.id, {\n onSuccess: () => navigateTo('/providers'),\n onError: (err) =>\n setError(err instanceof Error ? err.message : 'Could not archive this provider.'),\n });\n };\n\n return (\n <Modal title=\"Archive provider\" onClose={onClose}>\n <div className=\"space-y-4\">\n {error && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n Archive <span className=\"font-medium text-text-primary\">{provider.name}</span>? It will be\n removed from the active registry. This can be reversed by an administrator.\n </p>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleArchive}\n disabled={archive.isPending}\n className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm font-medium text-status-error-text hover:opacity-90 disabled:opacity-50\"\n >\n {archive.isPending ? 'Archiving…' : 'Archive provider'}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Price history — real observed price points for this provider from the shared\n// benchmark dataset (`subListBenchmarkDataPoints`).\n//\n// A provider's data points span several plans, regions, currencies and billing\n// intervals. Plotting them in one line would draw price moves that never\n// happened (a $20/mo Basic point next to a $240/yr Enterprise point reads as a\n// 12× hike). The series is therefore narrowed to the exact\n// (plan, region, currency, interval) tuple of the most recent observation, and\n// the card names that tuple so the reader knows what the line describes.\n// ---------------------------------------------------------------------------\nfunction ProviderPriceHistory({ slug }: { slug: string | null }) {\n const { data, isLoading, isError, refetch } = useBenchmarkDataPoints(slug);\n\n const series = useMemo(() => {\n const ordered = [...(data ?? [])]\n .filter((point) => Number.isFinite(point.amount))\n .sort((a, b) => new Date(a.observedAt).getTime() - new Date(b.observedAt).getTime());\n const latest = ordered[ordered.length - 1];\n if (!latest) return null;\n const scoped: SubBenchmarkDataPoint[] = ordered.filter(\n (point) =>\n point.planName === latest.planName &&\n point.region === latest.region &&\n point.currency === latest.currency &&\n point.interval === latest.interval\n );\n const amounts = scoped.map((point) => point.amount);\n return {\n points: scoped,\n latest,\n amounts,\n min: Math.min(...amounts),\n max: Math.max(...amounts),\n first: amounts[0] ?? latest.amount,\n };\n }, [data]);\n\n return (\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <LineChart className=\"h-4 w-4 text-text-secondary\" /> Price history\n </span>\n\n {!slug ? (\n <p className=\"mt-4 text-sm text-text-secondary\">\n Add a slug to this provider to match it against the shared price benchmark dataset.\n </p>\n ) : isLoading ? (\n <LoadingView className=\"min-h-[8rem]\" label=\"Loading price history…\" />\n ) : isError ? (\n <ErrorView\n className=\"min-h-[8rem]\"\n title=\"Price history unavailable\"\n message=\"Could not load observed prices for this provider.\"\n onRetry={() => void refetch()}\n />\n ) : !series ? (\n <p className=\"mt-4 text-sm text-text-secondary\">\n No price observations recorded for this provider yet.\n </p>\n ) : (\n <>\n <p className=\"mt-3 text-2xl font-bold tabular-nums text-text-primary\">\n {formatCurrency(series.latest.amount, series.latest.currency)}\n <span className=\"text-sm font-normal text-text-secondary\">\n {formatIntervalSuffix(series.latest.interval)}\n </span>\n </p>\n <p className=\"text-xs text-text-secondary\">\n {series.latest.planName} · {series.latest.region} · observed{' '}\n {formatDate(series.latest.observedAt)}\n </p>\n <Sparkline\n className=\"mt-3 w-full\"\n points={series.amounts}\n positive={series.latest.amount <= series.first}\n />\n <dl className=\"mt-3 space-y-2 border-t border-border-subtle pt-3 text-xs\">\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Observed range</dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">\n {formatCurrency(series.min, series.latest.currency)} –{' '}\n {formatCurrency(series.max, series.latest.currency)}\n </dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Observations</dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">{series.points.length}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Latest source</dt>\n <dd className=\"font-medium text-text-primary\">\n {series.latest.sourceLabel ?? formatEnumLabel(series.latest.sourceKind)}\n </dd>\n </div>\n </dl>\n </>\n )}\n </GlassCard>\n );\n}\n\nexport function ProviderDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useSubscriberBot();\n const { data: provider, isLoading, isError, refetch } = useProvider(id);\n const [showEdit, setShowEdit] = useState(false);\n const [showAddPlan, setShowAddPlan] = useState(false);\n const [showArchive, setShowArchive] = useState(false);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Provider\">\n <LoadingView label=\"Loading provider…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Provider\">\n <ErrorView message=\"Could not load this provider.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!provider) {\n return (\n <PageLayout title=\"Provider\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"Provider not found\"\n description=\"It may have been removed, or you may not have access to it.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Back to providers\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n const p = provider;\n // Only render the website as a link when it is a safe http(s) URL — websiteUrl\n // is user-controlled, so guard against javascript:/data: schemes and add\n // rel=\"noopener noreferrer\" to the target=\"_blank\" anchor.\n const safeWebsiteUrl = p.websiteUrl && /^https?:\\/\\//i.test(p.websiteUrl) ? p.websiteUrl : null;\n\n // Highlight the cheapest active priced plan as \"Best value\" (derived from real\n // plan data — guard for the no-plans / all-free case).\n const bestValuePlanId =\n p.plans && p.plans.length > 1\n ? (p.plans\n .filter((pl) => pl.isActive && pl.amount > 0)\n .reduce<SubPlan | null>(\n (min, pl) => (min === null || pl.amount < min.amount ? pl : min),\n null\n )?.id ?? null)\n : null;\n\n return (\n <PageLayout\n title={p.name}\n description={formatEnumLabel(p.category)}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setShowEdit(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n <button\n type=\"button\"\n onClick={() => setShowArchive(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-status-error-text/30 px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle\"\n >\n <Archive className=\"h-4 w-4\" /> Archive\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n </div>\n }\n >\n {/* Identity row */}\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"flex size-11 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <Building2 className=\"h-5 w-5\" />\n </span>\n <StatusBadge status={p.status} />\n {p.industry && <span className=\"text-sm text-text-secondary\">{p.industry}</span>}\n {p.region && <span className=\"text-sm text-text-secondary\">· {p.region}</span>}\n {safeWebsiteUrl && (\n <a\n href={safeWebsiteUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-sm text-text-link hover:underline\"\n >\n <Globe className=\"h-3.5 w-3.5\" /> Website\n </a>\n )}\n </div>\n\n {p.description && (\n <p className=\"mt-4 max-w-3xl text-sm leading-relaxed text-text-secondary\">\n {p.description}\n </p>\n )}\n\n {p.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap items-center gap-2\">\n <Tag className=\"h-3.5 w-3.5 text-text-secondary\" />\n {p.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Plans — tier cards */}\n <div className=\"lg:col-span-2\">\n <Section\n title=\"Plans\"\n description=\"Pricing this provider publishes\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddPlan(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add plan\n </button>\n }\n >\n {!p.plans || p.plans.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No published plans\"\n description=\"When this provider publishes plans, they'll appear here.\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddPlan(true)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Add plan\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {p.plans.map((plan) => {\n const isBestValue = plan.id === bestValuePlanId && p.plans!.length > 1;\n return (\n <GlassCard\n key={plan.id}\n treatment={isBestValue ? 'insight' : 'metric'}\n glow={isBestValue}\n className=\"flex flex-col p-5\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"text-sm font-semibold text-text-primary\">{plan.name}</p>\n {isBestValue ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-[11px] font-medium text-status-success-text\">\n <Sparkles className=\"h-3 w-3\" /> Best value\n </span>\n ) : (\n <StatusBadge status={plan.isActive ? 'ACTIVE' : 'DISABLED'} />\n )}\n </div>\n <p className=\"mt-3 text-2xl font-bold tabular-nums text-text-primary\">\n {formatCurrency(plan.amount, plan.currency)}\n <span className=\"text-sm font-normal text-text-secondary\">\n {formatIntervalSuffix(plan.interval)}\n </span>\n </p>\n {plan.description && (\n <p className=\"mt-2 text-xs leading-relaxed text-text-secondary\">\n {plan.description}\n </p>\n )}\n {plan.features.length > 0 && (\n <ul className=\"mt-4 space-y-2 border-t border-border-subtle pt-4\">\n {plan.features.slice(0, 6).map((f) => (\n <li\n key={f}\n className=\"flex items-start gap-2 text-xs text-text-secondary\"\n >\n <Check className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-status-success-text\" />\n <span>{f}</span>\n </li>\n ))}\n {plan.features.length > 6 && (\n <li className=\"pl-6 text-[11px] text-text-secondary\">\n +{plan.features.length - 6} more\n </li>\n )}\n </ul>\n )}\n </GlassCard>\n );\n })}\n </div>\n )}\n </Section>\n </div>\n\n {/* Sidebar — vendor health, churn risk, at-a-glance */}\n <div className=\"space-y-4\">\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center justify-between\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <ShieldCheck className=\"h-4 w-4 text-text-secondary\" /> Vendor health\n </span>\n {p.vendorHealthScore != null && (\n <span\n className={cn(\n 'rounded-full px-2 py-0.5 text-[11px] font-medium',\n healthMeta(p.vendorHealthScore).cls\n )}\n >\n {healthMeta(p.vendorHealthScore).label}\n </span>\n )}\n </div>\n {p.vendorHealthScore != null ? (\n <div className=\"mt-2 flex flex-col items-center\">\n <Gauge value={p.vendorHealthScore} max={100} size={168} label=\"out of 100\" />\n </div>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">No health score yet.</p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center justify-between\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <TrendingDown className=\"h-4 w-4 text-text-secondary\" /> Churn risk\n </span>\n {p.churnRiskScore != null && (\n <span\n className={cn(\n 'rounded-full px-2 py-0.5 text-[11px] font-medium',\n churnMeta(p.churnRiskScore).chip\n )}\n >\n {churnMeta(p.churnRiskScore).label}\n </span>\n )}\n </div>\n {p.churnRiskScore != null ? (\n <>\n <div className=\"mt-3 flex items-baseline justify-between\">\n <span className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {p.churnRiskScore}\n </span>\n <span className=\"text-xs text-text-secondary\">/ 100</span>\n </div>\n <MiniProgress\n value={p.churnRiskScore}\n tone={churnMeta(p.churnRiskScore).tone}\n className=\"mt-2\"\n />\n </>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">No churn signal yet.</p>\n )}\n </GlassCard>\n\n <ProviderPriceHistory slug={p.slug} />\n\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Layers className=\"h-4 w-4 text-text-secondary\" /> At a glance\n </span>\n <dl className=\"mt-3 space-y-2.5 text-sm\">\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Category</dt>\n <dd className=\"font-medium text-text-primary\">{formatEnumLabel(p.category)}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Industry</dt>\n <dd className=\"font-medium text-text-primary\">{p.industry ?? '—'}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Region</dt>\n <dd className=\"font-medium text-text-primary\">{p.region ?? '—'}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Language</dt>\n <dd className=\"font-medium text-text-primary\">{p.language ?? '—'}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Published plans</dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">\n {p.plans?.length ?? 0}\n </dd>\n </div>\n </dl>\n </GlassCard>\n </div>\n </div>\n\n {showEdit && <ProviderEditModal provider={p} onClose={() => setShowEdit(false)} />}\n {showAddPlan && <AddPlanModal providerId={p.id} onClose={() => setShowAddPlan(false)} />}\n {showArchive && <ArchiveProviderModal provider={p} onClose={() => setShowArchive(false)} />}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ import "../../components/index.js";
|
|
|
10
10
|
import { useCancelRelationship as h, useCreateAsset as g, useCreateEntitlement as _, useFileDownloadUrl as v, usePlans as y, useProviders as b, useRelationship as x, useTransitionRelationship as S, useUpdateRelationship as C, useUploadFile as w } from "../../hooks/useSubscriberBotApi.js";
|
|
11
11
|
import { useState as T } from "react";
|
|
12
12
|
import { Activity as E, ArrowLeft as D, Ban as O, CalendarClock as k, CreditCard as A, Download as j, Paperclip as M, Pause as N, Pencil as P, Play as F, Plus as I, ShieldCheck as L, Tag as ee, Wallet as te } from "lucide-react";
|
|
13
|
-
import { useParams as ne } from "react-router
|
|
13
|
+
import { useParams as ne } from "react-router";
|
|
14
14
|
import { Fragment as R, jsx as z, jsxs as B } from "react/jsx-runtime";
|
|
15
15
|
import { GlassCard as V } from "@burdenoff/fe-libs/ui";
|
|
16
16
|
//#region src/subscriberbot/pages/relationships/RelationshipDetailPage.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RelationshipDetailPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/relationships/RelationshipDetailPage.tsx"],"sourcesContent":["/**\n * Relationship detail\n * @module subscriberbot/pages/relationships\n *\n * Full per-relationship view: plan, price, billing period, next renewal,\n * health score, entitlements, attached assets and renewal events — plus\n * lifecycle transitions (validated server-side), edit, and cancel.\n * Reads `subRelationship(id)`; writes `subTransitionRelationship`,\n * `subCancelRelationship`, `subUpdateRelationship`, `subCreateEntitlement`,\n * `subCreateAsset`.\n */\n\nimport { useState, type ReactNode } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n CreditCard,\n Wallet,\n ShieldCheck,\n CalendarClock,\n Paperclip,\n Activity,\n Tag,\n Pencil,\n Plus,\n Pause,\n Play,\n Ban,\n Download,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Modal } from '../../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { Gauge, Timeline, MiniProgress } from '../../components';\nimport type { TimelineItem } from '../../components';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport {\n useRelationship,\n useTransitionRelationship,\n useCancelRelationship,\n useUpdateRelationship,\n useCreateEntitlement,\n useCreateAsset,\n useUploadFile,\n useFileDownloadUrl,\n useProviders,\n usePlans,\n} from '../../hooks/useSubscriberBotApi';\nimport {\n formatCurrency,\n formatDate,\n formatEnumLabel,\n formatIntervalSuffix,\n} from '../../utils/formatters';\nimport type {\n SubAssetType,\n SubBillingInterval,\n SubEntitlementType,\n SubRelationship,\n SubRelationshipStatus,\n SubUpdateRelationshipInput,\n} from '../../types';\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst INTERVALS: SubBillingInterval[] = [\n 'MONTHLY',\n 'YEARLY',\n 'QUARTERLY',\n 'WEEKLY',\n 'DAILY',\n 'USAGE_BASED',\n 'LIFETIME',\n 'FREE',\n];\n\nconst ENTITLEMENT_TYPES: SubEntitlementType[] = [\n 'FEATURE_ACCESS',\n 'SEAT',\n 'USAGE_QUOTA',\n 'CONTENT',\n 'DISCOUNT',\n 'SUPPORT_TIER',\n];\n\nconst ASSET_TYPES: SubAssetType[] = [\n 'RECEIPT',\n 'INVOICE',\n 'CONTRACT',\n 'LICENSE_KEY',\n 'CREDENTIAL',\n 'CERTIFICATE',\n 'WARRANTY',\n 'DOMAIN',\n];\n\n// Lifecycle actions surfaced as buttons; the server validates the transition.\nconst TRANSITIONS: Array<{ status: SubRelationshipStatus; label: string; icon: typeof Play }> = [\n { status: 'ACTIVE', label: 'Activate', icon: Play },\n { status: 'PAUSED', label: 'Pause', icon: Pause },\n { status: 'EXPIRED', label: 'Mark expired', icon: Ban },\n];\n\n/** Compact label/value row for the detail info cards. */\nfunction InfoRow({ label, value }: { label: string; value: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-3 border-b border-border-subtle/60 py-2.5 last:border-0 last:pb-0\">\n <span className=\"shrink-0 text-sm text-text-secondary\">{label}</span>\n <span className=\"min-w-0 truncate text-right text-sm font-medium text-text-primary\">\n {value}\n </span>\n </div>\n );\n}\n\n/** Map a 0–100 health score onto a MiniProgress tone. */\nfunction healthTone(\n score: number | null | undefined\n): 'positive' | 'accent' | 'warning' | 'negative' {\n if (score == null) return 'accent';\n if (score >= 66) return 'positive';\n if (score >= 33) return 'warning';\n return 'negative';\n}\n\n/** ISO string → yyyy-mm-dd for a native <input type=\"date\">. */\nfunction toDateInputValue(iso: string | null | undefined): string {\n if (!iso) return '';\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return '';\n return d.toISOString().slice(0, 10);\n}\n\n// ---------------------------------------------------------------------------\n// Edit relationship modal → subUpdateRelationship\n// ---------------------------------------------------------------------------\n\nfunction RelationshipEditModal({\n relationship,\n onClose,\n}: {\n relationship: SubRelationship;\n onClose: () => void;\n}) {\n const update = useUpdateRelationship();\n const providersQ = useProviders({ pagination: { limit: 100, offset: 0 } });\n\n const [label, setLabel] = useState(relationship.label);\n const [providerId, setProviderId] = useState(relationship.providerId ?? '');\n const [planId, setPlanId] = useState(relationship.planId ?? '');\n const [amount, setAmount] = useState(String(relationship.amount ?? ''));\n const [currency, setCurrency] = useState(relationship.currency);\n const [interval, setInterval] = useState<SubBillingInterval>(relationship.interval);\n const [renewsAt, setRenewsAt] = useState(toDateInputValue(relationship.renewsAt));\n const [autoRenew, setAutoRenew] = useState(relationship.autoRenew);\n const [healthScore, setHealthScore] = useState(\n relationship.healthScore != null ? String(relationship.healthScore) : ''\n );\n const [notes, setNotes] = useState(relationship.notes ?? '');\n const [formError, setFormError] = useState<string | null>(null);\n\n const plansQ = usePlans(providerId || undefined);\n\n // Ensure the currently-linked provider is always selectable even if it isn't\n // in the first page of the registry.\n const providerOptions = (() => {\n const list = providersQ.data?.items ?? [];\n if (relationship.provider && !list.some((p) => p.id === relationship.provider?.id)) {\n return [relationship.provider, ...list];\n }\n return list;\n })();\n const plans = plansQ.data ?? [];\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!label.trim()) {\n setFormError('A label is required.');\n return;\n }\n const input: SubUpdateRelationshipInput = {\n label: label.trim(),\n interval,\n autoRenew,\n currency: currency.trim() || 'USD',\n notes: notes.trim(),\n ...(providerId ? { providerId } : {}),\n ...(planId ? { planId } : {}),\n ...(amount.trim() ? { amount: Number(amount) } : {}),\n ...(healthScore.trim() ? { healthScore: Number(healthScore) } : {}),\n ...(renewsAt ? { renewsAt: new Date(renewsAt).toISOString() } : {}),\n };\n update.mutate(\n { id: relationship.id, input },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not update relationship.'),\n }\n );\n };\n\n return (\n <Modal title=\"Edit relationship\" onClose={onClose} maxWidthClassName=\"max-w-2xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n\n <div>\n <label htmlFor=\"edit-label\" className={labelCls}>\n Label <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"edit-label\"\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n className={inputCls}\n required\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"edit-provider\" className={labelCls}>\n Provider\n </label>\n <select\n id=\"edit-provider\"\n value={providerId}\n onChange={(e) => {\n setProviderId(e.target.value);\n setPlanId('');\n }}\n className={inputCls}\n >\n <option value=\"\">— None —</option>\n {providerOptions.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"edit-plan\" className={labelCls}>\n Plan\n </label>\n <select\n id=\"edit-plan\"\n value={planId}\n onChange={(e) => setPlanId(e.target.value)}\n className={inputCls}\n disabled={!providerId || plans.length === 0}\n >\n <option value=\"\">— None —</option>\n {plans.map((pl) => (\n <option key={pl.id} value={pl.id}>\n {pl.name}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"edit-amount\" className={labelCls}>\n Amount\n </label>\n <input\n id=\"edit-amount\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"edit-currency\" className={labelCls}>\n Currency\n </label>\n <input\n id=\"edit-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"edit-interval\" className={labelCls}>\n Billing interval\n </label>\n <select\n id=\"edit-interval\"\n value={interval}\n onChange={(e) => setInterval(e.target.value as SubBillingInterval)}\n className={inputCls}\n >\n {INTERVALS.map((i) => (\n <option key={i} value={i}>\n {i.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"edit-renews\" className={labelCls}>\n Renews on\n </label>\n <input\n id=\"edit-renews\"\n type=\"date\"\n value={renewsAt}\n onChange={(e) => setRenewsAt(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"edit-health\" className={labelCls}>\n Health score (0–100)\n </label>\n <input\n id=\"edit-health\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n value={healthScore}\n onChange={(e) => setHealthScore(e.target.value)}\n className={inputCls}\n />\n </div>\n <div className=\"flex items-end\">\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={autoRenew}\n onChange={(e) => setAutoRenew(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Auto-renews\n </label>\n </div>\n </div>\n\n <div>\n <label htmlFor=\"edit-notes\" className={labelCls}>\n Notes\n </label>\n <textarea\n id=\"edit-notes\"\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={3}\n className={inputCls}\n />\n </div>\n\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={update.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {update.isPending ? 'Saving…' : 'Save changes'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add entitlement modal → subCreateEntitlement\n// ---------------------------------------------------------------------------\n\nfunction AddEntitlementModal({\n relationshipId,\n onClose,\n}: {\n relationshipId: string;\n onClose: () => void;\n}) {\n const create = useCreateEntitlement();\n const [name, setName] = useState('');\n const [type, setType] = useState<SubEntitlementType>('FEATURE_ACCESS');\n const [quota, setQuota] = useState('');\n const [unit, setUnit] = useState('');\n const [description, setDescription] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('An entitlement name is required.');\n return;\n }\n create.mutate(\n {\n relationshipId,\n name: name.trim(),\n type,\n ...(quota.trim() ? { quota: Number(quota) } : {}),\n ...(unit.trim() ? { unit: unit.trim() } : {}),\n ...(description.trim() ? { description: description.trim() } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not add entitlement.'),\n }\n );\n };\n\n return (\n <Modal title=\"Add entitlement\" onClose={onClose}>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"ent-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"ent-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Seats, API calls, Priority support\"\n className={inputCls}\n required\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"ent-type\" className={labelCls}>\n Type\n </label>\n <select\n id=\"ent-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubEntitlementType)}\n className={inputCls}\n >\n {ENTITLEMENT_TYPES.map((t) => (\n <option key={t} value={t}>\n {t.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ent-quota\" className={labelCls}>\n Quota\n </label>\n <input\n id=\"ent-quota\"\n type=\"number\"\n min=\"0\"\n value={quota}\n onChange={(e) => setQuota(e.target.value)}\n placeholder=\"e.g. 5\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"ent-unit\" className={labelCls}>\n Unit\n </label>\n <input\n id=\"ent-unit\"\n type=\"text\"\n value={unit}\n onChange={(e) => setUnit(e.target.value)}\n placeholder=\"e.g. seats, GB\"\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"ent-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"ent-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={inputCls}\n />\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add entitlement'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Attach asset modal → upload to files-svc, then subCreateAsset\n// ---------------------------------------------------------------------------\n\nfunction AttachAssetModal({\n relationshipId,\n onClose,\n}: {\n relationshipId: string;\n onClose: () => void;\n}) {\n const create = useCreateAsset();\n const { uploadFile, progress, isUploading } = useUploadFile();\n const [name, setName] = useState('');\n const [type, setType] = useState<SubAssetType>('RECEIPT');\n const [file, setFile] = useState<File | null>(null);\n const [expiresAt, setExpiresAt] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const busy = isUploading || create.isPending;\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!file) {\n setFormError('Choose a file to attach.');\n return;\n }\n try {\n const { fileId, fileName } = await uploadFile(file);\n await create.mutateAsync({\n relationshipId,\n name: name.trim() || fileName,\n type,\n fileId,\n ...(expiresAt ? { expiresAt: new Date(expiresAt).toISOString() } : {}),\n });\n onClose();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Could not attach the asset.');\n }\n };\n\n return (\n <Modal title=\"Attach asset\" onClose={onClose}>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"asset-file\" className={labelCls}>\n File <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"asset-file\"\n type=\"file\"\n onChange={(e) => {\n const f = e.target.files?.[0] ?? null;\n setFile(f);\n if (f && !name.trim()) setName(f.name);\n }}\n className=\"w-full text-sm text-text-secondary file:mr-3 file:rounded-lg file:border file:border-border-subtle file:bg-bg-surface file:px-3 file:py-1.5 file:text-sm file:font-medium file:text-text-primary hover:file:bg-bg-sunken\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n Stored securely in files — only a reference is kept here.\n </p>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"asset-name\" className={labelCls}>\n Name\n </label>\n <input\n id=\"asset-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"Defaults to the file name\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"asset-type\" className={labelCls}>\n Type\n </label>\n <select\n id=\"asset-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubAssetType)}\n className={inputCls}\n >\n {ASSET_TYPES.map((t) => (\n <option key={t} value={t}>\n {t.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label htmlFor=\"asset-expires\" className={labelCls}>\n Expires on\n </label>\n <input\n id=\"asset-expires\"\n type=\"date\"\n value={expiresAt}\n onChange={(e) => setExpiresAt(e.target.value)}\n className={inputCls}\n />\n </div>\n {isUploading && <p className=\"text-xs text-text-secondary\">Uploading… {progress}%</p>}\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={busy}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {busy ? 'Attaching…' : 'Attach asset'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Asset row — resolves a presigned download URL on demand\n// ---------------------------------------------------------------------------\n\nfunction AssetRow({ asset }: { asset: NonNullable<SubRelationship['assets']>[number] }) {\n const getDownloadUrl = useFileDownloadUrl();\n const [downloading, setDownloading] = useState(false);\n\n const handleDownload = async () => {\n if (!asset.file?.id) return;\n setDownloading(true);\n try {\n const url = await getDownloadUrl(asset.file.id);\n window.open(url, '_blank', 'noopener,noreferrer');\n } catch {\n // Non-fatal: a broken/expired file reference just can't be downloaded.\n } finally {\n setDownloading(false);\n }\n };\n\n return (\n <div className=\"flex items-center justify-between gap-3 p-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <Paperclip className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{asset.name}</p>\n <p className=\"text-xs text-text-secondary\">{formatEnumLabel(asset.type)}</p>\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-3\">\n {asset.expiresAt && (\n <span className=\"text-xs text-text-secondary\">Expires {formatDate(asset.expiresAt)}</span>\n )}\n {asset.file?.id && (\n <button\n type=\"button\"\n onClick={handleDownload}\n disabled={downloading}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Download className=\"h-3.5 w-3.5\" /> {downloading ? 'Opening…' : 'Download'}\n </button>\n )}\n </div>\n </div>\n );\n}\n\nexport function RelationshipDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useSubscriberBot();\n const { data: relationship, isLoading, isError, refetch } = useRelationship(id);\n const transition = useTransitionRelationship();\n const cancel = useCancelRelationship();\n const [actionError, setActionError] = useState<string | null>(null);\n const [showEdit, setShowEdit] = useState(false);\n const [showAddEntitlement, setShowAddEntitlement] = useState(false);\n const [showAttachAsset, setShowAttachAsset] = useState(false);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Relationship\">\n <LoadingView label=\"Loading relationship…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Relationship\">\n <ErrorView message=\"Could not load this relationship.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!relationship) {\n return (\n <PageLayout title=\"Relationship\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"Relationship not found\"\n description=\"It may have been removed, or you may not have access to it.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Back to relationships\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n const r = relationship;\n const isBusy = transition.isPending || cancel.isPending;\n\n const runTransition = (status: SubRelationshipStatus) => {\n setActionError(null);\n transition.mutate(\n { id: r.id, status },\n { onError: (e) => setActionError(e instanceof Error ? e.message : 'Transition failed') }\n );\n };\n\n const runCancel = () => {\n setActionError(null);\n cancel.mutate(r.id, {\n onError: (e) => setActionError(e instanceof Error ? e.message : 'Cancel failed'),\n });\n };\n\n // Renewal timeline — built entirely from the real renewalEvents, oldest → newest.\n const renewalTimeline: TimelineItem[] = [...(r.renewalEvents ?? [])]\n .sort((a, b) => new Date(a.occursAt).getTime() - new Date(b.occursAt).getTime())\n .map((event) => {\n const ts = new Date(event.occursAt).getTime();\n const isFuture = !Number.isNaN(ts) && ts > Date.now();\n return {\n title: formatEnumLabel(event.eventType),\n sub: `${formatDate(event.occursAt)}${event.detail ? ` · ${event.detail}` : ''}`,\n meta:\n event.amount != null\n ? formatCurrency(event.amount, event.currency ?? r.currency)\n : undefined,\n tone: isFuture ? 'warning' : 'muted',\n };\n });\n\n const autoRenewPill = r.autoRenew ? (\n <span className=\"inline-flex items-center rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text\">\n On\n </span>\n ) : (\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n Off\n </span>\n );\n\n const availableTransitions = TRANSITIONS.filter((t) => t.status !== r.status);\n const planFeatures = r.plan?.features ?? [];\n\n return (\n <PageLayout\n title={r.label}\n description={r.provider?.name ?? formatEnumLabel(r.kind)}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setShowEdit(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n </div>\n }\n >\n {/* Hero summary + lifecycle actions */}\n <GlassCard treatment=\"glass\" glow className=\"p-5 sm:p-6\">\n <div className=\"flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {formatEnumLabel(r.kind)}\n </p>\n <div className=\"mt-2 flex flex-wrap items-center gap-3\">\n <span className=\"text-3xl font-bold tabular-nums text-text-primary\">\n {formatCurrency(r.amount, r.currency)}\n <span className=\"text-base font-normal text-text-secondary\">\n {formatIntervalSuffix(r.interval)}\n </span>\n </span>\n <StatusBadge status={r.status} />\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <CalendarClock className=\"h-3.5 w-3.5\" />\n {r.renewsAt ? `Renews ${formatDate(r.renewsAt)}` : 'No renewal scheduled'}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <ShieldCheck className=\"h-3.5 w-3.5\" />\n {formatEnumLabel(r.discoverySource)}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">Auto-renew {autoRenewPill}</span>\n </div>\n {r.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap items-center gap-2\">\n <Tag className=\"h-3.5 w-3.5 text-text-secondary\" />\n {r.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n </div>\n\n {/* Lifecycle actions */}\n <div className=\"flex shrink-0 flex-wrap gap-2 lg:flex-col lg:items-stretch\">\n {availableTransitions.map((t) => {\n const Icon = t.icon;\n return (\n <button\n key={t.status}\n type=\"button\"\n disabled={isBusy}\n onClick={() => runTransition(t.status)}\n className=\"inline-flex items-center justify-center gap-1.5 rounded-lg border border-border-subtle bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Icon className=\"h-3.5 w-3.5\" /> {t.label}\n </button>\n );\n })}\n {r.status !== 'CANCELLED' && (\n <button\n type=\"button\"\n disabled={isBusy}\n onClick={runCancel}\n className=\"inline-flex items-center justify-center gap-1.5 rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-3 py-1.5 text-xs font-medium text-status-error-text hover:opacity-90 disabled:opacity-50\"\n >\n <Ban className=\"h-3.5 w-3.5\" /> Cancel\n </button>\n )}\n </div>\n </div>\n </GlassCard>\n\n {actionError && (\n <div className=\"mt-4 rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {actionError}\n </div>\n )}\n\n {/* Main layout: details (left) + health & timeline rail (right) */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {/* Plan & pricing */}\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <CreditCard className=\"h-4 w-4 text-text-secondary\" /> Plan & pricing\n </div>\n <div className=\"mt-3\">\n <InfoRow label=\"Plan\" value={r.plan?.name ?? '—'} />\n <InfoRow\n label=\"Price\"\n value={\n <>\n {formatCurrency(r.amount, r.currency)}\n <span className=\"font-normal text-text-secondary\">\n {formatIntervalSuffix(r.interval)}\n </span>\n </>\n }\n />\n <InfoRow label=\"Billing interval\" value={formatEnumLabel(r.interval)} />\n <InfoRow label=\"Provider\" value={r.provider?.name ?? '—'} />\n </div>\n {planFeatures.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {planFeatures.map((f) => (\n <span\n key={f}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {f}\n </span>\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Payment & billing */}\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Wallet className=\"h-4 w-4 text-text-secondary\" /> Payment & billing\n </div>\n <div className=\"mt-3\">\n <InfoRow label=\"Currency\" value={r.currency} />\n <InfoRow label=\"Auto-renew\" value={autoRenewPill} />\n <InfoRow label=\"Next renewal\" value={r.renewsAt ? formatDate(r.renewsAt) : '—'} />\n <InfoRow label=\"Started\" value={r.startedAt ? formatDate(r.startedAt) : '—'} />\n {r.trialEndsAt && <InfoRow label=\"Trial ends\" value={formatDate(r.trialEndsAt)} />}\n </div>\n </GlassCard>\n </div>\n\n {/* Entitlements */}\n <Section\n className=\"mt-0\"\n title=\"Entitlements\"\n description=\"What this relationship grants you\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddEntitlement(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add entitlement\n </button>\n }\n >\n {!r.entitlements || r.entitlements.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No entitlements\"\n description=\"Entitlements (seats, quotas, feature access) will appear here.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {r.entitlements.map((e) => {\n const hasQuota = e.quota != null && e.quota > 0;\n return (\n <GlassCard key={e.id} treatment=\"plain\" className=\"p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"min-w-0 truncate text-sm font-medium text-text-primary\">\n {e.name}\n </p>\n <StatusBadge status={e.isActive ? 'ACTIVE' : 'DISABLED'} />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">{formatEnumLabel(e.type)}</p>\n {hasQuota && (\n <div className=\"mt-3\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {e.used} / {e.quota}\n {e.unit ? ` ${e.unit}` : ''}\n </span>\n <span className=\"tabular-nums\">\n {Math.round((e.used / (e.quota as number)) * 100)}%\n </span>\n </div>\n <MiniProgress\n value={e.used}\n max={e.quota as number}\n tone={\n e.used / (e.quota as number) >= 0.9\n ? 'negative'\n : e.used / (e.quota as number) >= 0.7\n ? 'warning'\n : 'accent'\n }\n className=\"mt-1.5\"\n />\n </div>\n )}\n </GlassCard>\n );\n })}\n </div>\n )}\n </Section>\n\n {/* Assets */}\n <Section\n className=\"mt-0\"\n title=\"Assets\"\n description=\"Invoices, contracts, licenses and credentials\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAttachAsset(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Attach asset\n </button>\n }\n >\n {!r.assets || r.assets.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No assets attached\"\n description=\"Attach invoices, contracts, licenses or receipts to this relationship.\"\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"divide-y divide-border-subtle p-0\">\n {r.assets.map((a) => (\n <AssetRow key={a.id} asset={a} />\n ))}\n </GlassCard>\n )}\n </Section>\n\n {r.notes && (\n <GlassCard treatment=\"plain\" className=\"p-4\">\n <p className=\"text-xs font-medium text-text-secondary\">Notes</p>\n <p className=\"mt-1 whitespace-pre-wrap text-sm text-text-primary\">{r.notes}</p>\n </GlassCard>\n )}\n </div>\n\n {/* Right rail: health gauge + renewal timeline */}\n <div className=\"space-y-4\">\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Activity className=\"h-4 w-4 text-text-secondary\" /> Health score\n </div>\n {r.healthScore != null ? (\n <div className=\"mt-4 flex flex-col items-center\">\n <Gauge value={r.healthScore} max={100} size={160} label=\"of 100\" />\n <div className=\"mt-2 w-full\">\n <MiniProgress value={r.healthScore} max={100} tone={healthTone(r.healthScore)} />\n </div>\n </div>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">\n Not scored yet. A health score appears once Subscriber Bot has enough signal.\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <CalendarClock className=\"h-4 w-4 text-text-secondary\" /> Renewal timeline\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Detected renewals and price changes\n </p>\n {renewalTimeline.length === 0 ? (\n <div className=\"mt-4\">\n <EmptyState\n illustration=\"empty-calendar\"\n title=\"No renewal events\"\n description=\"Renewals and price changes will appear here as they're detected.\"\n />\n </div>\n ) : (\n <Timeline items={renewalTimeline} className=\"mt-4\" />\n )}\n </GlassCard>\n </div>\n </div>\n\n {showEdit && <RelationshipEditModal relationship={r} onClose={() => setShowEdit(false)} />}\n {showAddEntitlement && (\n <AddEntitlementModal relationshipId={r.id} onClose={() => setShowAddEntitlement(false)} />\n )}\n {showAttachAsset && (\n <AttachAssetModal relationshipId={r.id} onClose={() => setShowAttachAsset(false)} />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RelationshipDetailPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/relationships/RelationshipDetailPage.tsx"],"sourcesContent":["/**\n * Relationship detail\n * @module subscriberbot/pages/relationships\n *\n * Full per-relationship view: plan, price, billing period, next renewal,\n * health score, entitlements, attached assets and renewal events — plus\n * lifecycle transitions (validated server-side), edit, and cancel.\n * Reads `subRelationship(id)`; writes `subTransitionRelationship`,\n * `subCancelRelationship`, `subUpdateRelationship`, `subCreateEntitlement`,\n * `subCreateAsset`.\n */\n\nimport { useState, type ReactNode } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n CreditCard,\n Wallet,\n ShieldCheck,\n CalendarClock,\n Paperclip,\n Activity,\n Tag,\n Pencil,\n Plus,\n Pause,\n Play,\n Ban,\n Download,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Modal } from '../../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { Gauge, Timeline, MiniProgress } from '../../components';\nimport type { TimelineItem } from '../../components';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport {\n useRelationship,\n useTransitionRelationship,\n useCancelRelationship,\n useUpdateRelationship,\n useCreateEntitlement,\n useCreateAsset,\n useUploadFile,\n useFileDownloadUrl,\n useProviders,\n usePlans,\n} from '../../hooks/useSubscriberBotApi';\nimport {\n formatCurrency,\n formatDate,\n formatEnumLabel,\n formatIntervalSuffix,\n} from '../../utils/formatters';\nimport type {\n SubAssetType,\n SubBillingInterval,\n SubEntitlementType,\n SubRelationship,\n SubRelationshipStatus,\n SubUpdateRelationshipInput,\n} from '../../types';\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst INTERVALS: SubBillingInterval[] = [\n 'MONTHLY',\n 'YEARLY',\n 'QUARTERLY',\n 'WEEKLY',\n 'DAILY',\n 'USAGE_BASED',\n 'LIFETIME',\n 'FREE',\n];\n\nconst ENTITLEMENT_TYPES: SubEntitlementType[] = [\n 'FEATURE_ACCESS',\n 'SEAT',\n 'USAGE_QUOTA',\n 'CONTENT',\n 'DISCOUNT',\n 'SUPPORT_TIER',\n];\n\nconst ASSET_TYPES: SubAssetType[] = [\n 'RECEIPT',\n 'INVOICE',\n 'CONTRACT',\n 'LICENSE_KEY',\n 'CREDENTIAL',\n 'CERTIFICATE',\n 'WARRANTY',\n 'DOMAIN',\n];\n\n// Lifecycle actions surfaced as buttons; the server validates the transition.\nconst TRANSITIONS: Array<{ status: SubRelationshipStatus; label: string; icon: typeof Play }> = [\n { status: 'ACTIVE', label: 'Activate', icon: Play },\n { status: 'PAUSED', label: 'Pause', icon: Pause },\n { status: 'EXPIRED', label: 'Mark expired', icon: Ban },\n];\n\n/** Compact label/value row for the detail info cards. */\nfunction InfoRow({ label, value }: { label: string; value: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-3 border-b border-border-subtle/60 py-2.5 last:border-0 last:pb-0\">\n <span className=\"shrink-0 text-sm text-text-secondary\">{label}</span>\n <span className=\"min-w-0 truncate text-right text-sm font-medium text-text-primary\">\n {value}\n </span>\n </div>\n );\n}\n\n/** Map a 0–100 health score onto a MiniProgress tone. */\nfunction healthTone(\n score: number | null | undefined\n): 'positive' | 'accent' | 'warning' | 'negative' {\n if (score == null) return 'accent';\n if (score >= 66) return 'positive';\n if (score >= 33) return 'warning';\n return 'negative';\n}\n\n/** ISO string → yyyy-mm-dd for a native <input type=\"date\">. */\nfunction toDateInputValue(iso: string | null | undefined): string {\n if (!iso) return '';\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return '';\n return d.toISOString().slice(0, 10);\n}\n\n// ---------------------------------------------------------------------------\n// Edit relationship modal → subUpdateRelationship\n// ---------------------------------------------------------------------------\n\nfunction RelationshipEditModal({\n relationship,\n onClose,\n}: {\n relationship: SubRelationship;\n onClose: () => void;\n}) {\n const update = useUpdateRelationship();\n const providersQ = useProviders({ pagination: { limit: 100, offset: 0 } });\n\n const [label, setLabel] = useState(relationship.label);\n const [providerId, setProviderId] = useState(relationship.providerId ?? '');\n const [planId, setPlanId] = useState(relationship.planId ?? '');\n const [amount, setAmount] = useState(String(relationship.amount ?? ''));\n const [currency, setCurrency] = useState(relationship.currency);\n const [interval, setInterval] = useState<SubBillingInterval>(relationship.interval);\n const [renewsAt, setRenewsAt] = useState(toDateInputValue(relationship.renewsAt));\n const [autoRenew, setAutoRenew] = useState(relationship.autoRenew);\n const [healthScore, setHealthScore] = useState(\n relationship.healthScore != null ? String(relationship.healthScore) : ''\n );\n const [notes, setNotes] = useState(relationship.notes ?? '');\n const [formError, setFormError] = useState<string | null>(null);\n\n const plansQ = usePlans(providerId || undefined);\n\n // Ensure the currently-linked provider is always selectable even if it isn't\n // in the first page of the registry.\n const providerOptions = (() => {\n const list = providersQ.data?.items ?? [];\n if (relationship.provider && !list.some((p) => p.id === relationship.provider?.id)) {\n return [relationship.provider, ...list];\n }\n return list;\n })();\n const plans = plansQ.data ?? [];\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!label.trim()) {\n setFormError('A label is required.');\n return;\n }\n const input: SubUpdateRelationshipInput = {\n label: label.trim(),\n interval,\n autoRenew,\n currency: currency.trim() || 'USD',\n notes: notes.trim(),\n ...(providerId ? { providerId } : {}),\n ...(planId ? { planId } : {}),\n ...(amount.trim() ? { amount: Number(amount) } : {}),\n ...(healthScore.trim() ? { healthScore: Number(healthScore) } : {}),\n ...(renewsAt ? { renewsAt: new Date(renewsAt).toISOString() } : {}),\n };\n update.mutate(\n { id: relationship.id, input },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not update relationship.'),\n }\n );\n };\n\n return (\n <Modal title=\"Edit relationship\" onClose={onClose} maxWidthClassName=\"max-w-2xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n\n <div>\n <label htmlFor=\"edit-label\" className={labelCls}>\n Label <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"edit-label\"\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n className={inputCls}\n required\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"edit-provider\" className={labelCls}>\n Provider\n </label>\n <select\n id=\"edit-provider\"\n value={providerId}\n onChange={(e) => {\n setProviderId(e.target.value);\n setPlanId('');\n }}\n className={inputCls}\n >\n <option value=\"\">— None —</option>\n {providerOptions.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"edit-plan\" className={labelCls}>\n Plan\n </label>\n <select\n id=\"edit-plan\"\n value={planId}\n onChange={(e) => setPlanId(e.target.value)}\n className={inputCls}\n disabled={!providerId || plans.length === 0}\n >\n <option value=\"\">— None —</option>\n {plans.map((pl) => (\n <option key={pl.id} value={pl.id}>\n {pl.name}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"edit-amount\" className={labelCls}>\n Amount\n </label>\n <input\n id=\"edit-amount\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"edit-currency\" className={labelCls}>\n Currency\n </label>\n <input\n id=\"edit-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"edit-interval\" className={labelCls}>\n Billing interval\n </label>\n <select\n id=\"edit-interval\"\n value={interval}\n onChange={(e) => setInterval(e.target.value as SubBillingInterval)}\n className={inputCls}\n >\n {INTERVALS.map((i) => (\n <option key={i} value={i}>\n {i.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"edit-renews\" className={labelCls}>\n Renews on\n </label>\n <input\n id=\"edit-renews\"\n type=\"date\"\n value={renewsAt}\n onChange={(e) => setRenewsAt(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"edit-health\" className={labelCls}>\n Health score (0–100)\n </label>\n <input\n id=\"edit-health\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n value={healthScore}\n onChange={(e) => setHealthScore(e.target.value)}\n className={inputCls}\n />\n </div>\n <div className=\"flex items-end\">\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={autoRenew}\n onChange={(e) => setAutoRenew(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Auto-renews\n </label>\n </div>\n </div>\n\n <div>\n <label htmlFor=\"edit-notes\" className={labelCls}>\n Notes\n </label>\n <textarea\n id=\"edit-notes\"\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={3}\n className={inputCls}\n />\n </div>\n\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={update.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {update.isPending ? 'Saving…' : 'Save changes'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add entitlement modal → subCreateEntitlement\n// ---------------------------------------------------------------------------\n\nfunction AddEntitlementModal({\n relationshipId,\n onClose,\n}: {\n relationshipId: string;\n onClose: () => void;\n}) {\n const create = useCreateEntitlement();\n const [name, setName] = useState('');\n const [type, setType] = useState<SubEntitlementType>('FEATURE_ACCESS');\n const [quota, setQuota] = useState('');\n const [unit, setUnit] = useState('');\n const [description, setDescription] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('An entitlement name is required.');\n return;\n }\n create.mutate(\n {\n relationshipId,\n name: name.trim(),\n type,\n ...(quota.trim() ? { quota: Number(quota) } : {}),\n ...(unit.trim() ? { unit: unit.trim() } : {}),\n ...(description.trim() ? { description: description.trim() } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not add entitlement.'),\n }\n );\n };\n\n return (\n <Modal title=\"Add entitlement\" onClose={onClose}>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"ent-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"ent-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Seats, API calls, Priority support\"\n className={inputCls}\n required\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"ent-type\" className={labelCls}>\n Type\n </label>\n <select\n id=\"ent-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubEntitlementType)}\n className={inputCls}\n >\n {ENTITLEMENT_TYPES.map((t) => (\n <option key={t} value={t}>\n {t.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ent-quota\" className={labelCls}>\n Quota\n </label>\n <input\n id=\"ent-quota\"\n type=\"number\"\n min=\"0\"\n value={quota}\n onChange={(e) => setQuota(e.target.value)}\n placeholder=\"e.g. 5\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"ent-unit\" className={labelCls}>\n Unit\n </label>\n <input\n id=\"ent-unit\"\n type=\"text\"\n value={unit}\n onChange={(e) => setUnit(e.target.value)}\n placeholder=\"e.g. seats, GB\"\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"ent-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"ent-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={inputCls}\n />\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add entitlement'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Attach asset modal → upload to files-svc, then subCreateAsset\n// ---------------------------------------------------------------------------\n\nfunction AttachAssetModal({\n relationshipId,\n onClose,\n}: {\n relationshipId: string;\n onClose: () => void;\n}) {\n const create = useCreateAsset();\n const { uploadFile, progress, isUploading } = useUploadFile();\n const [name, setName] = useState('');\n const [type, setType] = useState<SubAssetType>('RECEIPT');\n const [file, setFile] = useState<File | null>(null);\n const [expiresAt, setExpiresAt] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const busy = isUploading || create.isPending;\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!file) {\n setFormError('Choose a file to attach.');\n return;\n }\n try {\n const { fileId, fileName } = await uploadFile(file);\n await create.mutateAsync({\n relationshipId,\n name: name.trim() || fileName,\n type,\n fileId,\n ...(expiresAt ? { expiresAt: new Date(expiresAt).toISOString() } : {}),\n });\n onClose();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Could not attach the asset.');\n }\n };\n\n return (\n <Modal title=\"Attach asset\" onClose={onClose}>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"asset-file\" className={labelCls}>\n File <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"asset-file\"\n type=\"file\"\n onChange={(e) => {\n const f = e.target.files?.[0] ?? null;\n setFile(f);\n if (f && !name.trim()) setName(f.name);\n }}\n className=\"w-full text-sm text-text-secondary file:mr-3 file:rounded-lg file:border file:border-border-subtle file:bg-bg-surface file:px-3 file:py-1.5 file:text-sm file:font-medium file:text-text-primary hover:file:bg-bg-sunken\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n Stored securely in files — only a reference is kept here.\n </p>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"asset-name\" className={labelCls}>\n Name\n </label>\n <input\n id=\"asset-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"Defaults to the file name\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"asset-type\" className={labelCls}>\n Type\n </label>\n <select\n id=\"asset-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubAssetType)}\n className={inputCls}\n >\n {ASSET_TYPES.map((t) => (\n <option key={t} value={t}>\n {t.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label htmlFor=\"asset-expires\" className={labelCls}>\n Expires on\n </label>\n <input\n id=\"asset-expires\"\n type=\"date\"\n value={expiresAt}\n onChange={(e) => setExpiresAt(e.target.value)}\n className={inputCls}\n />\n </div>\n {isUploading && <p className=\"text-xs text-text-secondary\">Uploading… {progress}%</p>}\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={busy}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {busy ? 'Attaching…' : 'Attach asset'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Asset row — resolves a presigned download URL on demand\n// ---------------------------------------------------------------------------\n\nfunction AssetRow({ asset }: { asset: NonNullable<SubRelationship['assets']>[number] }) {\n const getDownloadUrl = useFileDownloadUrl();\n const [downloading, setDownloading] = useState(false);\n\n const handleDownload = async () => {\n if (!asset.file?.id) return;\n setDownloading(true);\n try {\n const url = await getDownloadUrl(asset.file.id);\n window.open(url, '_blank', 'noopener,noreferrer');\n } catch {\n // Non-fatal: a broken/expired file reference just can't be downloaded.\n } finally {\n setDownloading(false);\n }\n };\n\n return (\n <div className=\"flex items-center justify-between gap-3 p-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <Paperclip className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{asset.name}</p>\n <p className=\"text-xs text-text-secondary\">{formatEnumLabel(asset.type)}</p>\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-3\">\n {asset.expiresAt && (\n <span className=\"text-xs text-text-secondary\">Expires {formatDate(asset.expiresAt)}</span>\n )}\n {asset.file?.id && (\n <button\n type=\"button\"\n onClick={handleDownload}\n disabled={downloading}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Download className=\"h-3.5 w-3.5\" /> {downloading ? 'Opening…' : 'Download'}\n </button>\n )}\n </div>\n </div>\n );\n}\n\nexport function RelationshipDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useSubscriberBot();\n const { data: relationship, isLoading, isError, refetch } = useRelationship(id);\n const transition = useTransitionRelationship();\n const cancel = useCancelRelationship();\n const [actionError, setActionError] = useState<string | null>(null);\n const [showEdit, setShowEdit] = useState(false);\n const [showAddEntitlement, setShowAddEntitlement] = useState(false);\n const [showAttachAsset, setShowAttachAsset] = useState(false);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Relationship\">\n <LoadingView label=\"Loading relationship…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Relationship\">\n <ErrorView message=\"Could not load this relationship.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!relationship) {\n return (\n <PageLayout title=\"Relationship\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"Relationship not found\"\n description=\"It may have been removed, or you may not have access to it.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Back to relationships\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n const r = relationship;\n const isBusy = transition.isPending || cancel.isPending;\n\n const runTransition = (status: SubRelationshipStatus) => {\n setActionError(null);\n transition.mutate(\n { id: r.id, status },\n { onError: (e) => setActionError(e instanceof Error ? e.message : 'Transition failed') }\n );\n };\n\n const runCancel = () => {\n setActionError(null);\n cancel.mutate(r.id, {\n onError: (e) => setActionError(e instanceof Error ? e.message : 'Cancel failed'),\n });\n };\n\n // Renewal timeline — built entirely from the real renewalEvents, oldest → newest.\n const renewalTimeline: TimelineItem[] = [...(r.renewalEvents ?? [])]\n .sort((a, b) => new Date(a.occursAt).getTime() - new Date(b.occursAt).getTime())\n .map((event) => {\n const ts = new Date(event.occursAt).getTime();\n const isFuture = !Number.isNaN(ts) && ts > Date.now();\n return {\n title: formatEnumLabel(event.eventType),\n sub: `${formatDate(event.occursAt)}${event.detail ? ` · ${event.detail}` : ''}`,\n meta:\n event.amount != null\n ? formatCurrency(event.amount, event.currency ?? r.currency)\n : undefined,\n tone: isFuture ? 'warning' : 'muted',\n };\n });\n\n const autoRenewPill = r.autoRenew ? (\n <span className=\"inline-flex items-center rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text\">\n On\n </span>\n ) : (\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n Off\n </span>\n );\n\n const availableTransitions = TRANSITIONS.filter((t) => t.status !== r.status);\n const planFeatures = r.plan?.features ?? [];\n\n return (\n <PageLayout\n title={r.label}\n description={r.provider?.name ?? formatEnumLabel(r.kind)}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setShowEdit(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n </div>\n }\n >\n {/* Hero summary + lifecycle actions */}\n <GlassCard treatment=\"glass\" glow className=\"p-5 sm:p-6\">\n <div className=\"flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {formatEnumLabel(r.kind)}\n </p>\n <div className=\"mt-2 flex flex-wrap items-center gap-3\">\n <span className=\"text-3xl font-bold tabular-nums text-text-primary\">\n {formatCurrency(r.amount, r.currency)}\n <span className=\"text-base font-normal text-text-secondary\">\n {formatIntervalSuffix(r.interval)}\n </span>\n </span>\n <StatusBadge status={r.status} />\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <CalendarClock className=\"h-3.5 w-3.5\" />\n {r.renewsAt ? `Renews ${formatDate(r.renewsAt)}` : 'No renewal scheduled'}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <ShieldCheck className=\"h-3.5 w-3.5\" />\n {formatEnumLabel(r.discoverySource)}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">Auto-renew {autoRenewPill}</span>\n </div>\n {r.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap items-center gap-2\">\n <Tag className=\"h-3.5 w-3.5 text-text-secondary\" />\n {r.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n </div>\n\n {/* Lifecycle actions */}\n <div className=\"flex shrink-0 flex-wrap gap-2 lg:flex-col lg:items-stretch\">\n {availableTransitions.map((t) => {\n const Icon = t.icon;\n return (\n <button\n key={t.status}\n type=\"button\"\n disabled={isBusy}\n onClick={() => runTransition(t.status)}\n className=\"inline-flex items-center justify-center gap-1.5 rounded-lg border border-border-subtle bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Icon className=\"h-3.5 w-3.5\" /> {t.label}\n </button>\n );\n })}\n {r.status !== 'CANCELLED' && (\n <button\n type=\"button\"\n disabled={isBusy}\n onClick={runCancel}\n className=\"inline-flex items-center justify-center gap-1.5 rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-3 py-1.5 text-xs font-medium text-status-error-text hover:opacity-90 disabled:opacity-50\"\n >\n <Ban className=\"h-3.5 w-3.5\" /> Cancel\n </button>\n )}\n </div>\n </div>\n </GlassCard>\n\n {actionError && (\n <div className=\"mt-4 rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {actionError}\n </div>\n )}\n\n {/* Main layout: details (left) + health & timeline rail (right) */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {/* Plan & pricing */}\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <CreditCard className=\"h-4 w-4 text-text-secondary\" /> Plan & pricing\n </div>\n <div className=\"mt-3\">\n <InfoRow label=\"Plan\" value={r.plan?.name ?? '—'} />\n <InfoRow\n label=\"Price\"\n value={\n <>\n {formatCurrency(r.amount, r.currency)}\n <span className=\"font-normal text-text-secondary\">\n {formatIntervalSuffix(r.interval)}\n </span>\n </>\n }\n />\n <InfoRow label=\"Billing interval\" value={formatEnumLabel(r.interval)} />\n <InfoRow label=\"Provider\" value={r.provider?.name ?? '—'} />\n </div>\n {planFeatures.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {planFeatures.map((f) => (\n <span\n key={f}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {f}\n </span>\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Payment & billing */}\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Wallet className=\"h-4 w-4 text-text-secondary\" /> Payment & billing\n </div>\n <div className=\"mt-3\">\n <InfoRow label=\"Currency\" value={r.currency} />\n <InfoRow label=\"Auto-renew\" value={autoRenewPill} />\n <InfoRow label=\"Next renewal\" value={r.renewsAt ? formatDate(r.renewsAt) : '—'} />\n <InfoRow label=\"Started\" value={r.startedAt ? formatDate(r.startedAt) : '—'} />\n {r.trialEndsAt && <InfoRow label=\"Trial ends\" value={formatDate(r.trialEndsAt)} />}\n </div>\n </GlassCard>\n </div>\n\n {/* Entitlements */}\n <Section\n className=\"mt-0\"\n title=\"Entitlements\"\n description=\"What this relationship grants you\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddEntitlement(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add entitlement\n </button>\n }\n >\n {!r.entitlements || r.entitlements.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No entitlements\"\n description=\"Entitlements (seats, quotas, feature access) will appear here.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {r.entitlements.map((e) => {\n const hasQuota = e.quota != null && e.quota > 0;\n return (\n <GlassCard key={e.id} treatment=\"plain\" className=\"p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"min-w-0 truncate text-sm font-medium text-text-primary\">\n {e.name}\n </p>\n <StatusBadge status={e.isActive ? 'ACTIVE' : 'DISABLED'} />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">{formatEnumLabel(e.type)}</p>\n {hasQuota && (\n <div className=\"mt-3\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {e.used} / {e.quota}\n {e.unit ? ` ${e.unit}` : ''}\n </span>\n <span className=\"tabular-nums\">\n {Math.round((e.used / (e.quota as number)) * 100)}%\n </span>\n </div>\n <MiniProgress\n value={e.used}\n max={e.quota as number}\n tone={\n e.used / (e.quota as number) >= 0.9\n ? 'negative'\n : e.used / (e.quota as number) >= 0.7\n ? 'warning'\n : 'accent'\n }\n className=\"mt-1.5\"\n />\n </div>\n )}\n </GlassCard>\n );\n })}\n </div>\n )}\n </Section>\n\n {/* Assets */}\n <Section\n className=\"mt-0\"\n title=\"Assets\"\n description=\"Invoices, contracts, licenses and credentials\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAttachAsset(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Attach asset\n </button>\n }\n >\n {!r.assets || r.assets.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No assets attached\"\n description=\"Attach invoices, contracts, licenses or receipts to this relationship.\"\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"divide-y divide-border-subtle p-0\">\n {r.assets.map((a) => (\n <AssetRow key={a.id} asset={a} />\n ))}\n </GlassCard>\n )}\n </Section>\n\n {r.notes && (\n <GlassCard treatment=\"plain\" className=\"p-4\">\n <p className=\"text-xs font-medium text-text-secondary\">Notes</p>\n <p className=\"mt-1 whitespace-pre-wrap text-sm text-text-primary\">{r.notes}</p>\n </GlassCard>\n )}\n </div>\n\n {/* Right rail: health gauge + renewal timeline */}\n <div className=\"space-y-4\">\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Activity className=\"h-4 w-4 text-text-secondary\" /> Health score\n </div>\n {r.healthScore != null ? (\n <div className=\"mt-4 flex flex-col items-center\">\n <Gauge value={r.healthScore} max={100} size={160} label=\"of 100\" />\n <div className=\"mt-2 w-full\">\n <MiniProgress value={r.healthScore} max={100} tone={healthTone(r.healthScore)} />\n </div>\n </div>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">\n Not scored yet. A health score appears once Subscriber Bot has enough signal.\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <CalendarClock className=\"h-4 w-4 text-text-secondary\" /> Renewal timeline\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Detected renewals and price changes\n </p>\n {renewalTimeline.length === 0 ? (\n <div className=\"mt-4\">\n <EmptyState\n illustration=\"empty-calendar\"\n title=\"No renewal events\"\n description=\"Renewals and price changes will appear here as they're detected.\"\n />\n </div>\n ) : (\n <Timeline items={renewalTimeline} className=\"mt-4\" />\n )}\n </GlassCard>\n </div>\n </div>\n\n {showEdit && <RelationshipEditModal relationship={r} onClose={() => setShowEdit(false)} />}\n {showAddEntitlement && (\n <AddEntitlementModal relationshipId={r.id} onClose={() => setShowAddEntitlement(false)} />\n )}\n {showAttachAsset && (\n <AttachAssetModal relationshipId={r.id} onClose={() => setShowAttachAsset(false)} />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|