@burdenoff/microfe-subscriberbot 2026.1014.1 → 2026.1023.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/node_modules/clsx/dist/clsx.js.map +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +97 -18
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
- package/dist/subscriberbot/SubscriberBotRoot.js.map +1 -1
- package/dist/subscriberbot/SubscriberBotRoutes.js.map +1 -1
- package/dist/subscriberbot/admin/AdminDashboardPage.js.map +1 -1
- package/dist/subscriberbot/admin/AdminRoot.js.map +1 -1
- package/dist/subscriberbot/components/EmptyState.js.map +1 -1
- package/dist/subscriberbot/components/MetricsCard.js.map +1 -1
- package/dist/subscriberbot/components/Modal.js.map +1 -1
- package/dist/subscriberbot/components/PageLayout.js.map +1 -1
- package/dist/subscriberbot/components/StateViews.js.map +1 -1
- package/dist/subscriberbot/components/StatusBadge.js.map +1 -1
- package/dist/subscriberbot/components/viz.js.map +1 -1
- package/dist/subscriberbot/context/SubscriberBotContext.js.map +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts +19 -19
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts.map +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.js.map +1 -1
- package/dist/subscriberbot/hooks/useTr.js.map +1 -1
- package/dist/subscriberbot/pages/AssetVaultPage.js.map +1 -1
- package/dist/subscriberbot/pages/DashboardPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/DashboardPage.js +4 -4
- package/dist/subscriberbot/pages/DashboardPage.js.map +1 -1
- package/dist/subscriberbot/pages/InboxPage.js.map +1 -1
- package/dist/subscriberbot/pages/InsightsPage.js.map +1 -1
- package/dist/subscriberbot/pages/InvoicesPage.js.map +1 -1
- package/dist/subscriberbot/pages/WorkflowsPage.js.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentNewPage.js.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentsListPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderNewPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProvidersListPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipNewPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js.map +1 -1
- package/dist/subscriberbot/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/subscriberbot/store/subscriberbotStore.js.map +1 -1
- package/dist/subscriberbot/utils/cn.js.map +1 -1
- package/dist/subscriberbot/utils/formatters.js.map +1 -1
- package/dist/subscriberbot/utils/navigation.js.map +1 -1
- package/dist/subscriberbot/utils/pageContext.js.map +1 -1
- package/package.json +1 -1
|
@@ -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)` 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 { useTr } from '../../hooks/useTr';\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 CATEGORY_KEY: Record<SubProviderCategory, string> = {\n SAAS: 'providers.categories.saas',\n AI_SERVICE: 'providers.categories.aiService',\n STREAMING: 'providers.categories.streaming',\n NEWSLETTER: 'providers.categories.newsletter',\n NEWS_DIGEST: 'providers.categories.newsDigest',\n MEMBERSHIP: 'providers.categories.membership',\n COMMUNITY: 'providers.categories.community',\n EDUCATION: 'providers.categories.education',\n FINANCIAL: 'providers.categories.financial',\n INSURANCE: 'providers.categories.insurance',\n TELECOM: 'providers.categories.telecom',\n UTILITY: 'providers.categories.utility',\n ASSOCIATION: 'providers.categories.association',\n LICENSE: 'providers.categories.license',\n CONTRACT: 'providers.categories.contract',\n OTHER: 'providers.categories.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, tr: ReturnType<typeof useTr>): { label: string; cls: string } {\n if (score >= 70)\n return {\n label: tr('providers.detail.healthy', 'Healthy'),\n cls: 'bg-status-success-bg-subtle text-status-success-text',\n };\n if (score >= 45)\n return {\n label: tr('providers.detail.fair', 'Fair'),\n cls: 'bg-status-warning-bg-subtle text-status-warning-text',\n };\n return {\n label: tr('providers.detail.atRisk', 'At risk'),\n cls: 'bg-status-error-bg-subtle text-status-error-text',\n };\n}\n\n/** Churn-risk 0–100 → label + tone. Higher is worse. */\nfunction churnMeta(\n score: number,\n tr: ReturnType<typeof useTr>\n): {\n label: string;\n chip: string;\n tone: 'positive' | 'warning' | 'negative';\n} {\n if (score >= 67)\n return {\n label: tr('providers.detail.churnHigh', 'High'),\n chip: 'bg-status-error-bg-subtle text-status-error-text',\n tone: 'negative',\n };\n if (score >= 34)\n return {\n label: tr('providers.detail.churnModerate', 'Moderate'),\n chip: 'bg-status-warning-bg-subtle text-status-warning-text',\n tone: 'warning',\n };\n return {\n label: tr('providers.detail.churnLow', '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 tr = useTr();\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(tr('providers.new.nameRequired', '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(\n err instanceof Error\n ? err.message\n : tr('providers.detail.editModal.error', 'Could not update provider.')\n ),\n }\n );\n };\n\n return (\n <Modal\n title={tr('providers.detail.editModal.title', 'Edit provider')}\n onClose={onClose}\n maxWidthClassName=\"max-w-2xl\"\n >\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 {tr('providers.detail.editModal.nameLabel', 'Name')}{' '}\n <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 {tr('providers.detail.editModal.categoryLabel', '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 {tr(CATEGORY_KEY[c], formatEnumLabel(c))}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"prov-edit-status\" className={labelCls}>\n {tr('providers.detail.editModal.statusLabel', '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 {formatEnumLabel(s)}\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 {tr('providers.detail.editModal.industryLabel', '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 {tr('providers.detail.editModal.regionLabel', '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 {tr('providers.detail.editModal.languageLabel', '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 {tr('providers.detail.editModal.websiteLabel', '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={tr('providers.detail.editModal.websitePlaceholder', 'https://…')}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-desc\" className={labelCls}>\n {tr('providers.detail.editModal.descriptionLabel', '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 {tr('providers.detail.editModal.churnRiskLabel', '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 {tr('providers.detail.editModal.vendorHealthLabel', '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 {tr('providers.detail.editModal.tagsLabel', 'Tags')}{' '}\n <span className=\"text-text-secondary\">\n ({tr('providers.detail.editModal.tagsHint', 'comma-separated')})\n </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 {tr('providers.detail.editModal.cancel', '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\n ? tr('providers.detail.editModal.saving', 'Saving…')\n : tr('providers.detail.editModal.submit', '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 tr = useTr();\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(tr('providers.detail.addPlanModal.nameRequired', '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) =>\n setFormError(\n err instanceof Error\n ? err.message\n : tr('providers.detail.addPlanModal.error', 'Could not add plan.')\n ),\n }\n );\n };\n\n return (\n <Modal\n title={tr('providers.detail.addPlanModal.title', 'Add plan')}\n onClose={onClose}\n maxWidthClassName=\"max-w-xl\"\n >\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 {tr('providers.detail.addPlanModal.nameLabel', 'Name')}{' '}\n <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={tr(\n 'providers.detail.addPlanModal.namePlaceholder',\n 'e.g. Pro, Team, Enterprise'\n )}\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 {tr('providers.detail.addPlanModal.amountLabel', '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={tr('providers.detail.addPlanModal.amountPlaceholder', '0.00')}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-currency\" className={labelCls}>\n {tr('providers.detail.addPlanModal.currencyLabel', '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 {tr('providers.detail.addPlanModal.intervalLabel', '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 {formatEnumLabel(i)}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label htmlFor=\"plan-desc\" className={labelCls}>\n {tr('providers.detail.addPlanModal.descriptionLabel', '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 {tr('providers.detail.addPlanModal.featuresLabel', 'Features')}{' '}\n <span className=\"text-text-secondary\">\n ({tr('providers.detail.addPlanModal.featuresHint', 'one per line')})\n </span>\n </label>\n <textarea\n id=\"plan-features\"\n value={features}\n onChange={(e) => setFeatures(e.target.value)}\n rows={3}\n placeholder={tr(\n 'providers.detail.addPlanModal.featuresPlaceholder',\n 'Unlimited projects\\nPriority support\\nSSO'\n )}\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 {tr('providers.detail.addPlanModal.active', '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 {tr('providers.detail.addPlanModal.cancel', '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\n ? tr('providers.detail.addPlanModal.adding', 'Adding…')\n : tr('providers.detail.addPlanModal.submit', '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 tr = useTr();\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(\n err instanceof Error\n ? err.message\n : tr('providers.detail.archiveModal.error', 'Could not archive this provider.')\n ),\n });\n };\n\n return (\n <Modal title={tr('providers.detail.archiveModal.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 {tr(\n 'providers.detail.archiveModal.description',\n 'Archive {{name}}? It will be removed from the active registry. This can be reversed by an administrator.',\n { name: provider.name }\n )}\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 {tr('providers.detail.archiveModal.cancel', '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\n ? tr('providers.detail.archiveModal.archiving', 'Archiving…')\n : tr('providers.detail.archiveModal.submit', '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 tr = useTr();\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\" />{' '}\n {tr('providers.detail.priceHistory', 'Price history')}\n </span>\n\n {!slug ? (\n <p className=\"mt-4 text-sm text-text-secondary\">\n {tr(\n 'providers.detail.priceHistoryNoSlug',\n 'Add a slug to this provider to match it against the shared price benchmark dataset.'\n )}\n </p>\n ) : isLoading ? (\n <LoadingView\n className=\"min-h-[8rem]\"\n label={tr('providers.detail.priceHistoryLoading', 'Loading price history…')}\n />\n ) : isError ? (\n <ErrorView\n className=\"min-h-[8rem]\"\n title={tr('providers.detail.priceHistoryErrorTitle', 'Price history unavailable')}\n message={tr(\n 'providers.detail.priceHistoryError',\n 'Could not load observed prices for this provider.'\n )}\n onRetry={() => void refetch()}\n />\n ) : !series ? (\n <p className=\"mt-4 text-sm text-text-secondary\">\n {tr(\n 'providers.detail.priceHistoryEmpty',\n 'No price observations recorded for this provider yet.'\n )}\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} ·{' '}\n {tr('providers.detail.observed', 'observed')} {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\">\n {tr('providers.detail.observedRange', 'Observed range')}\n </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\">\n {tr('providers.detail.observations', 'Observations')}\n </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\">\n {tr('providers.detail.latestSource', 'Latest source')}\n </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 const tr = useTr();\n\n if (isLoading) {\n return (\n <PageLayout title={tr('providers.detail.loadingTitle', 'Provider')}>\n <LoadingView label={tr('providers.detail.loading', 'Loading provider…')} />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title={tr('providers.detail.loadingTitle', 'Provider')}>\n <ErrorView\n message={tr('providers.detail.error', 'Could not load this provider.')}\n onRetry={() => void refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!provider) {\n return (\n <PageLayout title={tr('providers.detail.loadingTitle', 'Provider')}>\n <EmptyState\n illustration=\"empty-data\"\n title={tr('providers.detail.notFoundTitle', 'Provider not found')}\n description={tr(\n 'providers.detail.notFoundDescription',\n 'It may have been removed, or you may not have access to it.'\n )}\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 {tr('providers.detail.backToProviders', '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={tr(CATEGORY_KEY[p.category], 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\" /> {tr('providers.detail.edit', '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\" /> {tr('providers.detail.archive', '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\" /> {tr('providers.detail.back', '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\" /> {tr('providers.detail.website', '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={tr('providers.detail.plans', 'Plans')}\n description={tr('providers.detail.plansDescription', '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\" /> {tr('providers.detail.addPlan', 'Add plan')}\n </button>\n }\n >\n {!p.plans || p.plans.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={tr('providers.detail.noPlansTitle', 'No published plans')}\n description={tr(\n 'providers.detail.noPlansDescription',\n \"When this provider publishes plans, they'll appear here.\"\n )}\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 {tr('providers.detail.addPlan', '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\" />{' '}\n {tr('providers.detail.bestValue', '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 {tr('providers.detail.moreFeatures', '+{{count}} more', {\n count: plan.features.length - 6,\n })}\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\" />{' '}\n {tr('providers.detail.vendorHealth', '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, tr).cls\n )}\n >\n {healthMeta(p.vendorHealthScore, tr).label}\n </span>\n )}\n </div>\n {p.vendorHealthScore != null ? (\n <div className=\"mt-2 flex flex-col items-center\">\n <Gauge\n value={p.vendorHealthScore}\n max={100}\n size={168}\n label={tr('providers.detail.outOf100', 'out of 100')}\n />\n </div>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">\n {tr('providers.detail.noHealthScore', 'No health score yet.')}\n </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\" />{' '}\n {tr('providers.detail.churnRisk', '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, tr).chip\n )}\n >\n {churnMeta(p.churnRiskScore, tr).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, tr).tone}\n className=\"mt-2\"\n />\n </>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">\n {tr('providers.detail.noChurnSignal', 'No churn signal yet.')}\n </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\" />{' '}\n {tr('providers.detail.atAGlance', '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\">\n {tr('providers.detail.category', 'Category')}\n </dt>\n <dd className=\"font-medium text-text-primary\">\n {tr(CATEGORY_KEY[p.category], formatEnumLabel(p.category))}\n </dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">\n {tr('providers.detail.industry', 'Industry')}\n </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\">{tr('providers.detail.region', '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\">\n {tr('providers.detail.language', 'Language')}\n </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\">\n {tr('providers.detail.publishedPlans', 'Published plans')}\n </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 { useTr } from '../../hooks/useTr';\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 CATEGORY_KEY: Record<SubProviderCategory, string> = {\n SAAS: 'providers.categories.saas',\n AI_SERVICE: 'providers.categories.aiService',\n STREAMING: 'providers.categories.streaming',\n NEWSLETTER: 'providers.categories.newsletter',\n NEWS_DIGEST: 'providers.categories.newsDigest',\n MEMBERSHIP: 'providers.categories.membership',\n COMMUNITY: 'providers.categories.community',\n EDUCATION: 'providers.categories.education',\n FINANCIAL: 'providers.categories.financial',\n INSURANCE: 'providers.categories.insurance',\n TELECOM: 'providers.categories.telecom',\n UTILITY: 'providers.categories.utility',\n ASSOCIATION: 'providers.categories.association',\n LICENSE: 'providers.categories.license',\n CONTRACT: 'providers.categories.contract',\n OTHER: 'providers.categories.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, tr: ReturnType<typeof useTr>): { label: string; cls: string } {\n if (score >= 70)\n return {\n label: tr('providers.detail.healthy', 'Healthy'),\n cls: 'bg-status-success-bg-subtle text-status-success-text',\n };\n if (score >= 45)\n return {\n label: tr('providers.detail.fair', 'Fair'),\n cls: 'bg-status-warning-bg-subtle text-status-warning-text',\n };\n return {\n label: tr('providers.detail.atRisk', 'At risk'),\n cls: 'bg-status-error-bg-subtle text-status-error-text',\n };\n}\n\n/** Churn-risk 0–100 → label + tone. Higher is worse. */\nfunction churnMeta(\n score: number,\n tr: ReturnType<typeof useTr>\n): {\n label: string;\n chip: string;\n tone: 'positive' | 'warning' | 'negative';\n} {\n if (score >= 67)\n return {\n label: tr('providers.detail.churnHigh', 'High'),\n chip: 'bg-status-error-bg-subtle text-status-error-text',\n tone: 'negative',\n };\n if (score >= 34)\n return {\n label: tr('providers.detail.churnModerate', 'Moderate'),\n chip: 'bg-status-warning-bg-subtle text-status-warning-text',\n tone: 'warning',\n };\n return {\n label: tr('providers.detail.churnLow', '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 tr = useTr();\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(tr('providers.new.nameRequired', '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(\n err instanceof Error\n ? err.message\n : tr('providers.detail.editModal.error', 'Could not update provider.')\n ),\n }\n );\n };\n\n return (\n <Modal\n title={tr('providers.detail.editModal.title', 'Edit provider')}\n onClose={onClose}\n maxWidthClassName=\"max-w-2xl\"\n >\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 {tr('providers.detail.editModal.nameLabel', 'Name')}{' '}\n <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 {tr('providers.detail.editModal.categoryLabel', '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 {tr(CATEGORY_KEY[c], formatEnumLabel(c))}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"prov-edit-status\" className={labelCls}>\n {tr('providers.detail.editModal.statusLabel', '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 {formatEnumLabel(s)}\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 {tr('providers.detail.editModal.industryLabel', '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 {tr('providers.detail.editModal.regionLabel', '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 {tr('providers.detail.editModal.languageLabel', '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 {tr('providers.detail.editModal.websiteLabel', '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={tr('providers.detail.editModal.websitePlaceholder', 'https://…')}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-desc\" className={labelCls}>\n {tr('providers.detail.editModal.descriptionLabel', '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 {tr('providers.detail.editModal.churnRiskLabel', '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 {tr('providers.detail.editModal.vendorHealthLabel', '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 {tr('providers.detail.editModal.tagsLabel', 'Tags')}{' '}\n <span className=\"text-text-secondary\">\n ({tr('providers.detail.editModal.tagsHint', 'comma-separated')})\n </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 {tr('providers.detail.editModal.cancel', '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\n ? tr('providers.detail.editModal.saving', 'Saving…')\n : tr('providers.detail.editModal.submit', '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 tr = useTr();\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(tr('providers.detail.addPlanModal.nameRequired', '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) =>\n setFormError(\n err instanceof Error\n ? err.message\n : tr('providers.detail.addPlanModal.error', 'Could not add plan.')\n ),\n }\n );\n };\n\n return (\n <Modal\n title={tr('providers.detail.addPlanModal.title', 'Add plan')}\n onClose={onClose}\n maxWidthClassName=\"max-w-xl\"\n >\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 {tr('providers.detail.addPlanModal.nameLabel', 'Name')}{' '}\n <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={tr(\n 'providers.detail.addPlanModal.namePlaceholder',\n 'e.g. Pro, Team, Enterprise'\n )}\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 {tr('providers.detail.addPlanModal.amountLabel', '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={tr('providers.detail.addPlanModal.amountPlaceholder', '0.00')}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-currency\" className={labelCls}>\n {tr('providers.detail.addPlanModal.currencyLabel', '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 {tr('providers.detail.addPlanModal.intervalLabel', '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 {formatEnumLabel(i)}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label htmlFor=\"plan-desc\" className={labelCls}>\n {tr('providers.detail.addPlanModal.descriptionLabel', '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 {tr('providers.detail.addPlanModal.featuresLabel', 'Features')}{' '}\n <span className=\"text-text-secondary\">\n ({tr('providers.detail.addPlanModal.featuresHint', 'one per line')})\n </span>\n </label>\n <textarea\n id=\"plan-features\"\n value={features}\n onChange={(e) => setFeatures(e.target.value)}\n rows={3}\n placeholder={tr(\n 'providers.detail.addPlanModal.featuresPlaceholder',\n 'Unlimited projects\\nPriority support\\nSSO'\n )}\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 {tr('providers.detail.addPlanModal.active', '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 {tr('providers.detail.addPlanModal.cancel', '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\n ? tr('providers.detail.addPlanModal.adding', 'Adding…')\n : tr('providers.detail.addPlanModal.submit', '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 tr = useTr();\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(\n err instanceof Error\n ? err.message\n : tr('providers.detail.archiveModal.error', 'Could not archive this provider.')\n ),\n });\n };\n\n return (\n <Modal title={tr('providers.detail.archiveModal.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 {tr(\n 'providers.detail.archiveModal.description',\n 'Archive {{name}}? It will be removed from the active registry. This can be reversed by an administrator.',\n { name: provider.name }\n )}\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 {tr('providers.detail.archiveModal.cancel', '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\n ? tr('providers.detail.archiveModal.archiving', 'Archiving…')\n : tr('providers.detail.archiveModal.submit', '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 tr = useTr();\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\" />{' '}\n {tr('providers.detail.priceHistory', 'Price history')}\n </span>\n\n {!slug ? (\n <p className=\"mt-4 text-sm text-text-secondary\">\n {tr(\n 'providers.detail.priceHistoryNoSlug',\n 'Add a slug to this provider to match it against the shared price benchmark dataset.'\n )}\n </p>\n ) : isLoading ? (\n <LoadingView\n className=\"min-h-[8rem]\"\n label={tr('providers.detail.priceHistoryLoading', 'Loading price history…')}\n />\n ) : isError ? (\n <ErrorView\n className=\"min-h-[8rem]\"\n title={tr('providers.detail.priceHistoryErrorTitle', 'Price history unavailable')}\n message={tr(\n 'providers.detail.priceHistoryError',\n 'Could not load observed prices for this provider.'\n )}\n onRetry={() => void refetch()}\n />\n ) : !series ? (\n <p className=\"mt-4 text-sm text-text-secondary\">\n {tr(\n 'providers.detail.priceHistoryEmpty',\n 'No price observations recorded for this provider yet.'\n )}\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} ·{' '}\n {tr('providers.detail.observed', 'observed')} {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\">\n {tr('providers.detail.observedRange', 'Observed range')}\n </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\">\n {tr('providers.detail.observations', 'Observations')}\n </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\">\n {tr('providers.detail.latestSource', 'Latest source')}\n </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 const tr = useTr();\n\n if (isLoading) {\n return (\n <PageLayout title={tr('providers.detail.loadingTitle', 'Provider')}>\n <LoadingView label={tr('providers.detail.loading', 'Loading provider…')} />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title={tr('providers.detail.loadingTitle', 'Provider')}>\n <ErrorView\n message={tr('providers.detail.error', 'Could not load this provider.')}\n onRetry={() => void refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!provider) {\n return (\n <PageLayout title={tr('providers.detail.loadingTitle', 'Provider')}>\n <EmptyState\n illustration=\"empty-data\"\n title={tr('providers.detail.notFoundTitle', 'Provider not found')}\n description={tr(\n 'providers.detail.notFoundDescription',\n 'It may have been removed, or you may not have access to it.'\n )}\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 {tr('providers.detail.backToProviders', '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={tr(CATEGORY_KEY[p.category], 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\" /> {tr('providers.detail.edit', '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\" /> {tr('providers.detail.archive', '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\" /> {tr('providers.detail.back', '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\" /> {tr('providers.detail.website', '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={tr('providers.detail.plans', 'Plans')}\n description={tr('providers.detail.plansDescription', '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\" /> {tr('providers.detail.addPlan', 'Add plan')}\n </button>\n }\n >\n {!p.plans || p.plans.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={tr('providers.detail.noPlansTitle', 'No published plans')}\n description={tr(\n 'providers.detail.noPlansDescription',\n \"When this provider publishes plans, they'll appear here.\"\n )}\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 {tr('providers.detail.addPlan', '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\" />{' '}\n {tr('providers.detail.bestValue', '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 {tr('providers.detail.moreFeatures', '+{{count}} more', {\n count: plan.features.length - 6,\n })}\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\" />{' '}\n {tr('providers.detail.vendorHealth', '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, tr).cls\n )}\n >\n {healthMeta(p.vendorHealthScore, tr).label}\n </span>\n )}\n </div>\n {p.vendorHealthScore != null ? (\n <div className=\"mt-2 flex flex-col items-center\">\n <Gauge\n value={p.vendorHealthScore}\n max={100}\n size={168}\n label={tr('providers.detail.outOf100', 'out of 100')}\n />\n </div>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">\n {tr('providers.detail.noHealthScore', 'No health score yet.')}\n </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\" />{' '}\n {tr('providers.detail.churnRisk', '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, tr).chip\n )}\n >\n {churnMeta(p.churnRiskScore, tr).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, tr).tone}\n className=\"mt-2\"\n />\n </>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">\n {tr('providers.detail.noChurnSignal', 'No churn signal yet.')}\n </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\" />{' '}\n {tr('providers.detail.atAGlance', '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\">\n {tr('providers.detail.category', 'Category')}\n </dt>\n <dd className=\"font-medium text-text-primary\">\n {tr(CATEGORY_KEY[p.category], formatEnumLabel(p.category))}\n </dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">\n {tr('providers.detail.industry', 'Industry')}\n </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\">{tr('providers.detail.region', '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\">\n {tr('providers.detail.language', 'Language')}\n </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\">\n {tr('providers.detail.publishedPlans', 'Published plans')}\n </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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderNewPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/providers/ProviderNewPage.tsx"],"sourcesContent":["/**\n * Create provider\n * @module subscriberbot/pages/providers\n *\n * Add a provider to the registry. Writes `subCreateProvider`. A card-wrapped\n * form with category chips and a live marketplace-style preview that mirrors\n * exactly what will land in the registry.\n */\n\nimport { useState } from 'react';\nimport { ArrowLeft, Building2, Sparkles } from 'lucide-react';\nimport { PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport { useCreateProvider } from '../../hooks/useSubscriberBotApi';\nimport { useTr } from '../../hooks/useTr';\nimport { cn } from '../../utils/cn';\nimport { formatEnumLabel } from '../../utils/formatters';\nimport type { SubCreateProviderInput, SubProviderCategory } from '../../types';\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 CATEGORY_KEY: Record<SubProviderCategory, string> = {\n SAAS: 'providers.categories.saas',\n AI_SERVICE: 'providers.categories.aiService',\n STREAMING: 'providers.categories.streaming',\n NEWSLETTER: 'providers.categories.newsletter',\n NEWS_DIGEST: 'providers.categories.newsDigest',\n MEMBERSHIP: 'providers.categories.membership',\n COMMUNITY: 'providers.categories.community',\n EDUCATION: 'providers.categories.education',\n FINANCIAL: 'providers.categories.financial',\n INSURANCE: 'providers.categories.insurance',\n TELECOM: 'providers.categories.telecom',\n UTILITY: 'providers.categories.utility',\n ASSOCIATION: 'providers.categories.association',\n LICENSE: 'providers.categories.license',\n CONTRACT: 'providers.categories.contract',\n OTHER: 'providers.categories.other',\n};\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\nexport function ProviderNewPage() {\n const tr = useTr();\n const { navigateTo } = useSubscriberBot();\n const create = useCreateProvider();\n\n const [name, setName] = useState('');\n const [category, setCategory] = useState<SubProviderCategory>('SAAS');\n const [description, setDescription] = useState('');\n const [websiteUrl, setWebsiteUrl] = useState('');\n const [region, setRegion] = useState('');\n const [industry, setIndustry] = useState('');\n const [tags, setTags] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const parsedTags = tags\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError(tr('providers.new.nameRequired', 'A provider name is required.'));\n return;\n }\n const input: SubCreateProviderInput = {\n name: name.trim(),\n category,\n ...(description.trim() ? { description: description.trim() } : {}),\n ...(websiteUrl.trim() ? { websiteUrl: websiteUrl.trim() } : {}),\n ...(region.trim() ? { region: region.trim() } : {}),\n ...(industry.trim() ? { industry: industry.trim() } : {}),\n ...(parsedTags.length ? { tags: parsedTags } : {}),\n };\n create.mutate(input, {\n onSuccess: (provider) => navigateTo(`/providers/${provider.id}`),\n onError: (err) =>\n setFormError(\n err instanceof Error\n ? err.message\n : tr('providers.new.error', 'Could not create provider.')\n ),\n });\n };\n\n return (\n <PageLayout\n title={tr('providers.new.title', 'Add provider')}\n description={tr('providers.new.description', 'Register a new provider in the registry')}\n actions={\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\" /> {tr('providers.new.back', 'Back')}\n </button>\n }\n >\n <PagePurpose className=\"mb-6\">\n {tr(\n 'providers.new.purpose',\n \"Add a provider that isn't in the registry yet so you can attach relationships, plans and assets to it.\"\n )}\n </PagePurpose>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Form */}\n <GlassCard treatment=\"metric\" className=\"p-6 lg:col-span-2\">\n <form onSubmit={handleSubmit} className=\"space-y-5\">\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=\"prov-name\" className={labelCls}>\n {tr('providers.new.nameLabel', 'Name')}{' '}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"prov-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder={tr('providers.new.namePlaceholder', 'e.g. OpenAI, Netflix, AWS')}\n className={inputCls}\n required\n />\n </div>\n\n <div>\n <span className={labelCls}>{tr('providers.new.categoryLabel', 'Category')}</span>\n <div\n className=\"flex flex-wrap gap-2\"\n role=\"group\"\n aria-label={tr('providers.new.categoryLabel', 'Category')}\n >\n {CATEGORIES.map((c) => {\n const active = category === c;\n return (\n <button\n key={c}\n type=\"button\"\n aria-pressed={active}\n onClick={() => setCategory(c)}\n className={cn(\n 'rounded-full border px-3 py-1.5 text-xs font-medium transition-colors',\n active\n ? 'border-transparent bg-action-primary-bg text-action-primary-text'\n : 'border-border-subtle bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n {tr(CATEGORY_KEY[c], formatEnumLabel(c))}\n </button>\n );\n })}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"prov-website\" className={labelCls}>\n {tr('providers.new.websiteLabel', 'Website')}\n </label>\n <input\n id=\"prov-website\"\n type=\"url\"\n value={websiteUrl}\n onChange={(e) => setWebsiteUrl(e.target.value)}\n placeholder={tr('providers.new.websitePlaceholder', 'https://…')}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-region\" className={labelCls}>\n {tr('providers.new.regionLabel', 'Region')}\n </label>\n <input\n id=\"prov-region\"\n type=\"text\"\n value={region}\n onChange={(e) => setRegion(e.target.value)}\n placeholder={tr('providers.new.regionPlaceholder', 'e.g. Global, US, EU')}\n className={inputCls}\n />\n </div>\n </div>\n\n <div>\n <label htmlFor=\"prov-industry\" className={labelCls}>\n {tr('providers.new.industryLabel', 'Industry')}\n </label>\n <input\n id=\"prov-industry\"\n type=\"text\"\n value={industry}\n onChange={(e) => setIndustry(e.target.value)}\n placeholder={tr('providers.new.industryPlaceholder', 'e.g. Cloud, Media')}\n className={inputCls}\n />\n </div>\n\n <div>\n <label htmlFor=\"prov-desc\" className={labelCls}>\n {tr('providers.new.descriptionLabel', 'Description')}\n </label>\n <textarea\n id=\"prov-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={3}\n placeholder={tr(\n 'providers.new.descriptionPlaceholder',\n 'What does this provider offer?'\n )}\n className={inputCls}\n />\n </div>\n\n <div>\n <label htmlFor=\"prov-tags\" className={labelCls}>\n {tr('providers.new.tagsLabel', 'Tags')}{' '}\n <span className=\"text-text-secondary\">\n ({tr('providers.new.tagsHint', 'comma-separated')})\n </span>\n </label>\n <input\n id=\"prov-tags\"\n type=\"text\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n placeholder={tr('providers.new.tagsPlaceholder', 'ai, productivity, enterprise')}\n className={inputCls}\n />\n </div>\n\n <div className=\"flex flex-wrap gap-3 border-t border-border-subtle pt-5\">\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-5 py-2.5 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending\n ? tr('providers.new.creating', 'Creating…')\n : tr('providers.new.submit', 'Create provider')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers')}\n className=\"rounded-lg border border-border-subtle px-5 py-2.5 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n {tr('providers.new.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n </GlassCard>\n\n {/* Live preview — reflects the form state (no fabricated data) */}\n <div className=\"lg:sticky lg:top-6 lg:self-start\">\n <p className=\"mb-2 inline-flex items-center gap-1.5 text-xs font-medium text-text-secondary\">\n <Sparkles className=\"h-3.5 w-3.5\" /> {tr('providers.new.preview', 'Registry preview')}\n </p>\n <GlassCard treatment=\"glass\" className=\"flex flex-col p-5\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"flex size-10 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <Building2 className=\"h-5 w-5\" />\n </span>\n <span className=\"rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-[11px] font-medium text-status-warning-text\">\n {tr('providers.new.unverified', 'Unverified')}\n </span>\n </div>\n <p className=\"mt-3 truncate text-base font-semibold text-text-primary\">\n {name.trim() || tr('providers.new.previewName', 'Provider name')}\n </p>\n <span className=\"mt-1 inline-flex w-fit rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n {tr(CATEGORY_KEY[category], formatEnumLabel(category))}\n </span>\n {description.trim() ? (\n <p className=\"mt-3 line-clamp-3 text-xs leading-relaxed text-text-secondary\">\n {description.trim()}\n </p>\n ) : (\n <p className=\"mt-3 text-xs italic text-text-secondary/70\">\n {tr('providers.new.previewDescription', 'A short description will appear here.')}\n </p>\n )}\n {(region.trim() || industry.trim() || parsedTags.length > 0) && (\n <div className=\"mt-4 flex flex-wrap items-center gap-2 border-t border-border-subtle pt-3\">\n {industry.trim() && (\n <span className=\"text-[11px] text-text-secondary\">{industry.trim()}</span>\n )}\n {region.trim() && (\n <span className=\"text-[11px] text-text-secondary\">· {region.trim()}</span>\n )}\n {parsedTags.slice(0, 4).map((t) => (\n <span\n key={t}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-[11px] text-text-secondary\"\n >\n {t}\n </span>\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProviderNewPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/providers/ProviderNewPage.tsx"],"sourcesContent":["/**\n * Create provider\n * @module subscriberbot/pages/providers\n *\n * Add a provider to the registry. Writes `subCreateProvider`. A card-wrapped\n * form with category chips and a live marketplace-style preview that mirrors\n * exactly what will land in the registry.\n */\n\nimport { useState } from 'react';\nimport { ArrowLeft, Building2, Sparkles } from 'lucide-react';\nimport { PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport { useCreateProvider } from '../../hooks/useSubscriberBotApi';\nimport { useTr } from '../../hooks/useTr';\nimport { cn } from '../../utils/cn';\nimport { formatEnumLabel } from '../../utils/formatters';\nimport type { SubCreateProviderInput, SubProviderCategory } from '../../types';\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 CATEGORY_KEY: Record<SubProviderCategory, string> = {\n SAAS: 'providers.categories.saas',\n AI_SERVICE: 'providers.categories.aiService',\n STREAMING: 'providers.categories.streaming',\n NEWSLETTER: 'providers.categories.newsletter',\n NEWS_DIGEST: 'providers.categories.newsDigest',\n MEMBERSHIP: 'providers.categories.membership',\n COMMUNITY: 'providers.categories.community',\n EDUCATION: 'providers.categories.education',\n FINANCIAL: 'providers.categories.financial',\n INSURANCE: 'providers.categories.insurance',\n TELECOM: 'providers.categories.telecom',\n UTILITY: 'providers.categories.utility',\n ASSOCIATION: 'providers.categories.association',\n LICENSE: 'providers.categories.license',\n CONTRACT: 'providers.categories.contract',\n OTHER: 'providers.categories.other',\n};\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\nexport function ProviderNewPage() {\n const tr = useTr();\n const { navigateTo } = useSubscriberBot();\n const create = useCreateProvider();\n\n const [name, setName] = useState('');\n const [category, setCategory] = useState<SubProviderCategory>('SAAS');\n const [description, setDescription] = useState('');\n const [websiteUrl, setWebsiteUrl] = useState('');\n const [region, setRegion] = useState('');\n const [industry, setIndustry] = useState('');\n const [tags, setTags] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const parsedTags = tags\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError(tr('providers.new.nameRequired', 'A provider name is required.'));\n return;\n }\n const input: SubCreateProviderInput = {\n name: name.trim(),\n category,\n ...(description.trim() ? { description: description.trim() } : {}),\n ...(websiteUrl.trim() ? { websiteUrl: websiteUrl.trim() } : {}),\n ...(region.trim() ? { region: region.trim() } : {}),\n ...(industry.trim() ? { industry: industry.trim() } : {}),\n ...(parsedTags.length ? { tags: parsedTags } : {}),\n };\n create.mutate(input, {\n onSuccess: (provider) => navigateTo(`/providers/${provider.id}`),\n onError: (err) =>\n setFormError(\n err instanceof Error\n ? err.message\n : tr('providers.new.error', 'Could not create provider.')\n ),\n });\n };\n\n return (\n <PageLayout\n title={tr('providers.new.title', 'Add provider')}\n description={tr('providers.new.description', 'Register a new provider in the registry')}\n actions={\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\" /> {tr('providers.new.back', 'Back')}\n </button>\n }\n >\n <PagePurpose className=\"mb-6\">\n {tr(\n 'providers.new.purpose',\n \"Add a provider that isn't in the registry yet so you can attach relationships, plans and assets to it.\"\n )}\n </PagePurpose>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Form */}\n <GlassCard treatment=\"metric\" className=\"p-6 lg:col-span-2\">\n <form onSubmit={handleSubmit} className=\"space-y-5\">\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=\"prov-name\" className={labelCls}>\n {tr('providers.new.nameLabel', 'Name')}{' '}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"prov-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder={tr('providers.new.namePlaceholder', 'e.g. OpenAI, Netflix, AWS')}\n className={inputCls}\n required\n />\n </div>\n\n <div>\n <span className={labelCls}>{tr('providers.new.categoryLabel', 'Category')}</span>\n <div\n className=\"flex flex-wrap gap-2\"\n role=\"group\"\n aria-label={tr('providers.new.categoryLabel', 'Category')}\n >\n {CATEGORIES.map((c) => {\n const active = category === c;\n return (\n <button\n key={c}\n type=\"button\"\n aria-pressed={active}\n onClick={() => setCategory(c)}\n className={cn(\n 'rounded-full border px-3 py-1.5 text-xs font-medium transition-colors',\n active\n ? 'border-transparent bg-action-primary-bg text-action-primary-text'\n : 'border-border-subtle bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n {tr(CATEGORY_KEY[c], formatEnumLabel(c))}\n </button>\n );\n })}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"prov-website\" className={labelCls}>\n {tr('providers.new.websiteLabel', 'Website')}\n </label>\n <input\n id=\"prov-website\"\n type=\"url\"\n value={websiteUrl}\n onChange={(e) => setWebsiteUrl(e.target.value)}\n placeholder={tr('providers.new.websitePlaceholder', 'https://…')}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-region\" className={labelCls}>\n {tr('providers.new.regionLabel', 'Region')}\n </label>\n <input\n id=\"prov-region\"\n type=\"text\"\n value={region}\n onChange={(e) => setRegion(e.target.value)}\n placeholder={tr('providers.new.regionPlaceholder', 'e.g. Global, US, EU')}\n className={inputCls}\n />\n </div>\n </div>\n\n <div>\n <label htmlFor=\"prov-industry\" className={labelCls}>\n {tr('providers.new.industryLabel', 'Industry')}\n </label>\n <input\n id=\"prov-industry\"\n type=\"text\"\n value={industry}\n onChange={(e) => setIndustry(e.target.value)}\n placeholder={tr('providers.new.industryPlaceholder', 'e.g. Cloud, Media')}\n className={inputCls}\n />\n </div>\n\n <div>\n <label htmlFor=\"prov-desc\" className={labelCls}>\n {tr('providers.new.descriptionLabel', 'Description')}\n </label>\n <textarea\n id=\"prov-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={3}\n placeholder={tr(\n 'providers.new.descriptionPlaceholder',\n 'What does this provider offer?'\n )}\n className={inputCls}\n />\n </div>\n\n <div>\n <label htmlFor=\"prov-tags\" className={labelCls}>\n {tr('providers.new.tagsLabel', 'Tags')}{' '}\n <span className=\"text-text-secondary\">\n ({tr('providers.new.tagsHint', 'comma-separated')})\n </span>\n </label>\n <input\n id=\"prov-tags\"\n type=\"text\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n placeholder={tr('providers.new.tagsPlaceholder', 'ai, productivity, enterprise')}\n className={inputCls}\n />\n </div>\n\n <div className=\"flex flex-wrap gap-3 border-t border-border-subtle pt-5\">\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-5 py-2.5 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending\n ? tr('providers.new.creating', 'Creating…')\n : tr('providers.new.submit', 'Create provider')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers')}\n className=\"rounded-lg border border-border-subtle px-5 py-2.5 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n {tr('providers.new.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n </GlassCard>\n\n {/* Live preview — reflects the form state (no fabricated data) */}\n <div className=\"lg:sticky lg:top-6 lg:self-start\">\n <p className=\"mb-2 inline-flex items-center gap-1.5 text-xs font-medium text-text-secondary\">\n <Sparkles className=\"h-3.5 w-3.5\" /> {tr('providers.new.preview', 'Registry preview')}\n </p>\n <GlassCard treatment=\"glass\" className=\"flex flex-col p-5\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"flex size-10 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <Building2 className=\"h-5 w-5\" />\n </span>\n <span className=\"rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-[11px] font-medium text-status-warning-text\">\n {tr('providers.new.unverified', 'Unverified')}\n </span>\n </div>\n <p className=\"mt-3 truncate text-base font-semibold text-text-primary\">\n {name.trim() || tr('providers.new.previewName', 'Provider name')}\n </p>\n <span className=\"mt-1 inline-flex w-fit rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n {tr(CATEGORY_KEY[category], formatEnumLabel(category))}\n </span>\n {description.trim() ? (\n <p className=\"mt-3 line-clamp-3 text-xs leading-relaxed text-text-secondary\">\n {description.trim()}\n </p>\n ) : (\n <p className=\"mt-3 text-xs italic text-text-secondary/70\">\n {tr('providers.new.previewDescription', 'A short description will appear here.')}\n </p>\n )}\n {(region.trim() || industry.trim() || parsedTags.length > 0) && (\n <div className=\"mt-4 flex flex-wrap items-center gap-2 border-t border-border-subtle pt-3\">\n {industry.trim() && (\n <span className=\"text-[11px] text-text-secondary\">{industry.trim()}</span>\n )}\n {region.trim() && (\n <span className=\"text-[11px] text-text-secondary\">· {region.trim()}</span>\n )}\n {parsedTags.slice(0, 4).map((t) => (\n <span\n key={t}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-[11px] text-text-secondary\"\n >\n {t}\n </span>\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProvidersListPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/providers/ProvidersListPage.tsx"],"sourcesContent":["/**\n * Providers list (Global Provider Registry)\n * @module subscriberbot/pages/providers\n *\n * Discovery marketplace: search and browse the provider registry. Server-side\n * search + category/status filters + pagination via `subListProviders`\n * (a SubProviderConnection). Category filter chips, a responsive grid of glass\n * provider cards with a real vendor-health indicator + churn hint, and a small\n * live summary strip computed from the fetched page.\n */\n\nimport { useState } from 'react';\nimport {\n Plus,\n Search,\n Building2,\n ShieldCheck,\n ChevronRight,\n Store,\n Activity,\n BadgeCheck,\n} from 'lucide-react';\nimport { EmphasisPanel, PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { MiniProgress } from '../../components';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport { useProviders } from '../../hooks/useSubscriberBotApi';\nimport { useTr } from '../../hooks/useTr';\nimport { cn } from '../../utils/cn';\nimport { formatEnumLabel } from '../../utils/formatters';\nimport type { SubProvider, SubProviderCategory, SubProviderStatus } from '../../types';\n\nconst PAGE_SIZE = 24;\n\nconst CATEGORY_KEY: Record<SubProviderCategory, string> = {\n SAAS: 'providers.categories.saas',\n AI_SERVICE: 'providers.categories.aiService',\n STREAMING: 'providers.categories.streaming',\n NEWSLETTER: 'providers.categories.newsletter',\n NEWS_DIGEST: 'providers.categories.newsDigest',\n MEMBERSHIP: 'providers.categories.membership',\n COMMUNITY: 'providers.categories.community',\n EDUCATION: 'providers.categories.education',\n FINANCIAL: 'providers.categories.financial',\n INSURANCE: 'providers.categories.insurance',\n TELECOM: 'providers.categories.telecom',\n UTILITY: 'providers.categories.utility',\n ASSOCIATION: 'providers.categories.association',\n LICENSE: 'providers.categories.license',\n CONTRACT: 'providers.categories.contract',\n OTHER: 'providers.categories.other',\n};\n\nconst STATUS_KEY: Record<SubProviderStatus, string> = {\n VERIFIED: 'providers.filters.verified',\n CLAIMED: 'providers.filters.claimed',\n UNVERIFIED: 'providers.filters.unverified',\n DEPRECATED: 'providers.filters.deprecated',\n};\n\n/** Map a 0–100 vendor-health score to a MiniProgress tone. Higher is healthier. */\nfunction healthTone(score: number): 'positive' | 'accent' | 'warning' | 'negative' {\n if (score >= 70) return 'positive';\n if (score >= 45) return 'accent';\n if (score >= 25) return 'warning';\n return 'negative';\n}\n\n/** Map a 0–100 churn-risk score to a label + semantic tone. Higher is worse. */\nfunction churnMeta(score: number, tr: ReturnType<typeof useTr>): { label: string; cls: string } {\n if (score >= 67)\n return {\n label: tr('providers.card.highChurn', 'High churn risk'),\n cls: 'bg-status-error-bg-subtle text-status-error-text',\n };\n if (score >= 34)\n return {\n label: tr('providers.card.moderateChurn', 'Moderate churn risk'),\n cls: 'bg-status-warning-bg-subtle text-status-warning-text',\n };\n return {\n label: tr('providers.card.lowChurn', 'Low churn risk'),\n cls: 'bg-status-success-bg-subtle text-status-success-text',\n };\n}\n\nfunction ProviderCard({ provider, onOpen }: { provider: SubProvider; onOpen: () => void }) {\n const tr = useTr();\n const health = provider.vendorHealthScore;\n const churn = provider.churnRiskScore;\n const churnInfo = churn != null ? churnMeta(churn, tr) : null;\n\n return (\n <GlassCard\n treatment=\"glass\"\n role=\"button\"\n tabIndex={0}\n onClick={onOpen}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onOpen();\n }\n }}\n aria-label={tr('providers.card.openAria', 'Open {{name}}', { name: provider.name })}\n className=\"group flex cursor-pointer flex-col p-5 outline-none transition-all hover:-translate-y-0.5 hover:shadow-lg focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"flex size-10 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={provider.status} />\n </div>\n\n <p className=\"mt-3 truncate text-base font-semibold text-text-primary\">{provider.name}</p>\n <span className=\"mt-1 inline-flex w-fit rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n {tr(CATEGORY_KEY[provider.category], formatEnumLabel(provider.category))}\n </span>\n\n {provider.description ? (\n <p className=\"mt-3 line-clamp-2 min-h-[2.5rem] text-xs leading-relaxed text-text-secondary\">\n {provider.description}\n </p>\n ) : (\n <p className=\"mt-3 min-h-[2.5rem] text-xs italic text-text-secondary/70\">\n {tr('providers.card.noDescription', 'No description yet.')}\n </p>\n )}\n\n <div className=\"mt-4 border-t border-border-subtle pt-3\">\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"inline-flex items-center gap-1 text-text-secondary\">\n <ShieldCheck className=\"h-3.5 w-3.5\" />{' '}\n {tr('providers.card.vendorHealth', 'Vendor health')}\n </span>\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {health != null ? `${health}/100` : '—'}\n </span>\n </div>\n <MiniProgress\n value={health ?? 0}\n tone={health != null ? healthTone(health) : 'accent'}\n className=\"mt-1.5\"\n />\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {churnInfo && (\n <span\n className={cn(\n 'inline-flex rounded-full px-2 py-0.5 text-[11px] font-medium',\n churnInfo.cls\n )}\n >\n {churnInfo.label}\n </span>\n )}\n {provider.region && (\n <span className=\"text-[11px] text-text-secondary\">{provider.region}</span>\n )}\n </div>\n <span className=\"inline-flex items-center gap-0.5 text-xs font-medium text-text-link opacity-0 transition-opacity group-hover:opacity-100\">\n {tr('providers.card.view', 'View')} <ChevronRight className=\"h-3.5 w-3.5\" />\n </span>\n </div>\n </GlassCard>\n );\n}\n\nexport function ProvidersListPage() {\n const tr = useTr();\n const { navigateTo } = useSubscriberBot();\n const [page, setPage] = useState(0);\n const [search, setSearch] = useState('');\n const [searchInput, setSearchInput] = useState('');\n const [category, setCategory] = useState<'' | SubProviderCategory>('');\n const [status, setStatus] = useState<'' | SubProviderStatus>('');\n\n const categoryOptions: Array<{ value: '' | SubProviderCategory; label: string }> = [\n { value: '', label: tr('providers.categories.all', 'All') },\n ...Object.entries(CATEGORY_KEY).map(([value, key]) => ({\n value: value as SubProviderCategory,\n label: tr(key, formatEnumLabel(value as SubProviderCategory)),\n })),\n ];\n\n const statusOptions: Array<{ value: '' | SubProviderStatus; label: string }> = [\n { value: '', label: tr('providers.filters.anyStatus', 'Any status') },\n ...Object.entries(STATUS_KEY).map(([value, key]) => ({\n value: value as SubProviderStatus,\n label: tr(key, formatEnumLabel(value as SubProviderStatus)),\n })),\n ];\n\n const { data, isLoading, isError, refetch, isFetching } = useProviders({\n search: search || undefined,\n category: category || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = data?.items ?? [];\n const hasMore = data?.pageInfo.hasMore ?? false;\n\n // Live summary strip — computed from the fetched page (never fabricated).\n const verifiedCount = items.filter((p) => p.status === 'VERIFIED').length;\n const healthScores = items.map((p) => p.vendorHealthScore).filter((s): s is number => s != null);\n const avgHealth = healthScores.length\n ? Math.round(healthScores.reduce((a, b) => a + b, 0) / healthScores.length)\n : null;\n\n const submitSearch = (e: React.FormEvent) => {\n e.preventDefault();\n setSearch(searchInput.trim());\n setPage(0);\n };\n\n const summary: Array<{ icon: typeof Store; label: string; value: string }> = [\n {\n icon: Store,\n label: tr('providers.summary.onThisPage', 'On this page'),\n value: String(items.length),\n },\n {\n icon: BadgeCheck,\n label: tr('providers.filters.verified', 'Verified'),\n value: String(verifiedCount),\n },\n {\n icon: Activity,\n label: tr('providers.summary.avgVendorHealth', 'Avg. vendor health'),\n value: avgHealth != null ? `${avgHealth}/100` : '—',\n },\n ];\n\n return (\n <PageLayout\n title={tr('providers.title', 'Discover providers')}\n description={tr(\n 'providers.description',\n 'Browse the global provider registry — the public face of the Subscription Knowledge Graph'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" /> {tr('providers.addProvider', 'Add provider')}\n </button>\n }\n >\n <PagePurpose className=\"mb-4\">\n {tr(\n 'providers.purpose',\n 'Every provider exists here whether or not it integrates. Search and filter to find the ones you have relationships with, compare their vendor health, or add a new one.'\n )}\n </PagePurpose>\n\n <EmphasisPanel>\n <form onSubmit={submitSearch} className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"text\"\n placeholder={tr('providers.searchPlaceholder', 'Search providers by name…')}\n value={searchInput}\n onChange={(e) => setSearchInput(e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2.5 pl-10 pr-24 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <button\n type=\"submit\"\n className=\"absolute right-1.5 top-1/2 -translate-y-1/2 rounded-md bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text hover:opacity-90\"\n >\n {tr('providers.search', 'Search')}\n </button>\n </form>\n </EmphasisPanel>\n\n {/* Category filter chips */}\n <div className=\"mt-4 flex items-center gap-2 overflow-x-auto pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\">\n {categoryOptions.map((o) => {\n const active = category === o.value;\n return (\n <button\n key={o.value || 'all'}\n type=\"button\"\n aria-pressed={active}\n onClick={() => {\n setCategory(o.value);\n setPage(0);\n }}\n className={cn(\n 'shrink-0 rounded-full border px-3.5 py-1.5 text-xs font-medium transition-colors',\n active\n ? 'border-transparent bg-action-primary-bg text-action-primary-text'\n : 'border-border-subtle bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n {o.label}\n </button>\n );\n })}\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-2\">\n <select\n aria-label={tr('providers.filters.statusLabel', 'Filter by status')}\n value={status}\n onChange={(e) => {\n setStatus(e.target.value as '' | SubProviderStatus);\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {statusOptions.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Live summary strip */}\n {!isLoading && !isError && items.length > 0 && (\n <div className=\"mt-4 grid grid-cols-1 gap-3 sm:grid-cols-3\">\n {summary.map((s) => (\n <GlassCard key={s.label} treatment=\"metric\" className=\"flex items-center gap-3 p-4\">\n <span className=\"flex size-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <s.icon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs text-text-secondary\">{s.label}</p>\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">{s.value}</p>\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n\n {isLoading ? (\n <LoadingView label={tr('providers.loading', 'Loading providers…')} />\n ) : isError ? (\n <ErrorView\n message={tr('providers.error', 'Could not load the provider registry.')}\n onRetry={() => void refetch()}\n />\n ) : items.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-search\"\n icon={<Building2 className=\"h-12 w-12\" />}\n title={tr('providers.emptyTitle', 'No providers found')}\n description={\n search\n ? tr('providers.emptyDescriptionSearch', 'No providers match \"{{search}}\".', {\n search,\n })\n : tr(\n 'providers.emptyDescription',\n 'No providers in the registry with these filters yet.'\n )\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers/new')}\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 {tr('providers.addProvider', 'Add provider')}\n </button>\n }\n />\n </div>\n ) : (\n <>\n <div className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {items.map((p) => (\n <ProviderCard\n key={p.id}\n provider={p}\n onOpen={() => navigateTo(`/providers/${p.id}`)}\n />\n ))}\n </div>\n\n <div className=\"mt-6 flex items-center justify-between\">\n <p className=\"text-xs text-text-secondary\">\n {tr('providers.pagination.page', 'Page {{page}}', { page: page + 1 })}\n {isFetching ? ` · ${tr('providers.pagination.refreshing', 'refreshing…')}` : ''}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n disabled={page === 0}\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n page === 0\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n {tr('providers.pagination.previous', 'Previous')}\n </button>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setPage((p) => p + 1)}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n !hasMore\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n {tr('providers.pagination.next', 'Next')}\n </button>\n </div>\n </div>\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProvidersListPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/providers/ProvidersListPage.tsx"],"sourcesContent":["/**\n * Providers list (Global Provider Registry)\n * @module subscriberbot/pages/providers\n *\n * Discovery marketplace: search and browse the provider registry. Server-side\n * search + category/status filters + pagination via `subListProviders`\n * (a SubProviderConnection). Category filter chips, a responsive grid of glass\n * provider cards with a real vendor-health indicator + churn hint, and a small\n * live summary strip computed from the fetched page.\n */\n\nimport { useState } from 'react';\nimport {\n Plus,\n Search,\n Building2,\n ShieldCheck,\n ChevronRight,\n Store,\n Activity,\n BadgeCheck,\n} from 'lucide-react';\nimport { EmphasisPanel, PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { MiniProgress } from '../../components';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport { useProviders } from '../../hooks/useSubscriberBotApi';\nimport { useTr } from '../../hooks/useTr';\nimport { cn } from '../../utils/cn';\nimport { formatEnumLabel } from '../../utils/formatters';\nimport type { SubProvider, SubProviderCategory, SubProviderStatus } from '../../types';\n\nconst PAGE_SIZE = 24;\n\nconst CATEGORY_KEY: Record<SubProviderCategory, string> = {\n SAAS: 'providers.categories.saas',\n AI_SERVICE: 'providers.categories.aiService',\n STREAMING: 'providers.categories.streaming',\n NEWSLETTER: 'providers.categories.newsletter',\n NEWS_DIGEST: 'providers.categories.newsDigest',\n MEMBERSHIP: 'providers.categories.membership',\n COMMUNITY: 'providers.categories.community',\n EDUCATION: 'providers.categories.education',\n FINANCIAL: 'providers.categories.financial',\n INSURANCE: 'providers.categories.insurance',\n TELECOM: 'providers.categories.telecom',\n UTILITY: 'providers.categories.utility',\n ASSOCIATION: 'providers.categories.association',\n LICENSE: 'providers.categories.license',\n CONTRACT: 'providers.categories.contract',\n OTHER: 'providers.categories.other',\n};\n\nconst STATUS_KEY: Record<SubProviderStatus, string> = {\n VERIFIED: 'providers.filters.verified',\n CLAIMED: 'providers.filters.claimed',\n UNVERIFIED: 'providers.filters.unverified',\n DEPRECATED: 'providers.filters.deprecated',\n};\n\n/** Map a 0–100 vendor-health score to a MiniProgress tone. Higher is healthier. */\nfunction healthTone(score: number): 'positive' | 'accent' | 'warning' | 'negative' {\n if (score >= 70) return 'positive';\n if (score >= 45) return 'accent';\n if (score >= 25) return 'warning';\n return 'negative';\n}\n\n/** Map a 0–100 churn-risk score to a label + semantic tone. Higher is worse. */\nfunction churnMeta(score: number, tr: ReturnType<typeof useTr>): { label: string; cls: string } {\n if (score >= 67)\n return {\n label: tr('providers.card.highChurn', 'High churn risk'),\n cls: 'bg-status-error-bg-subtle text-status-error-text',\n };\n if (score >= 34)\n return {\n label: tr('providers.card.moderateChurn', 'Moderate churn risk'),\n cls: 'bg-status-warning-bg-subtle text-status-warning-text',\n };\n return {\n label: tr('providers.card.lowChurn', 'Low churn risk'),\n cls: 'bg-status-success-bg-subtle text-status-success-text',\n };\n}\n\nfunction ProviderCard({ provider, onOpen }: { provider: SubProvider; onOpen: () => void }) {\n const tr = useTr();\n const health = provider.vendorHealthScore;\n const churn = provider.churnRiskScore;\n const churnInfo = churn != null ? churnMeta(churn, tr) : null;\n\n return (\n <GlassCard\n treatment=\"glass\"\n role=\"button\"\n tabIndex={0}\n onClick={onOpen}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onOpen();\n }\n }}\n aria-label={tr('providers.card.openAria', 'Open {{name}}', { name: provider.name })}\n className=\"group flex cursor-pointer flex-col p-5 outline-none transition-all hover:-translate-y-0.5 hover:shadow-lg focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"flex size-10 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={provider.status} />\n </div>\n\n <p className=\"mt-3 truncate text-base font-semibold text-text-primary\">{provider.name}</p>\n <span className=\"mt-1 inline-flex w-fit rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n {tr(CATEGORY_KEY[provider.category], formatEnumLabel(provider.category))}\n </span>\n\n {provider.description ? (\n <p className=\"mt-3 line-clamp-2 min-h-[2.5rem] text-xs leading-relaxed text-text-secondary\">\n {provider.description}\n </p>\n ) : (\n <p className=\"mt-3 min-h-[2.5rem] text-xs italic text-text-secondary/70\">\n {tr('providers.card.noDescription', 'No description yet.')}\n </p>\n )}\n\n <div className=\"mt-4 border-t border-border-subtle pt-3\">\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"inline-flex items-center gap-1 text-text-secondary\">\n <ShieldCheck className=\"h-3.5 w-3.5\" />{' '}\n {tr('providers.card.vendorHealth', 'Vendor health')}\n </span>\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {health != null ? `${health}/100` : '—'}\n </span>\n </div>\n <MiniProgress\n value={health ?? 0}\n tone={health != null ? healthTone(health) : 'accent'}\n className=\"mt-1.5\"\n />\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {churnInfo && (\n <span\n className={cn(\n 'inline-flex rounded-full px-2 py-0.5 text-[11px] font-medium',\n churnInfo.cls\n )}\n >\n {churnInfo.label}\n </span>\n )}\n {provider.region && (\n <span className=\"text-[11px] text-text-secondary\">{provider.region}</span>\n )}\n </div>\n <span className=\"inline-flex items-center gap-0.5 text-xs font-medium text-text-link opacity-0 transition-opacity group-hover:opacity-100\">\n {tr('providers.card.view', 'View')} <ChevronRight className=\"h-3.5 w-3.5\" />\n </span>\n </div>\n </GlassCard>\n );\n}\n\nexport function ProvidersListPage() {\n const tr = useTr();\n const { navigateTo } = useSubscriberBot();\n const [page, setPage] = useState(0);\n const [search, setSearch] = useState('');\n const [searchInput, setSearchInput] = useState('');\n const [category, setCategory] = useState<'' | SubProviderCategory>('');\n const [status, setStatus] = useState<'' | SubProviderStatus>('');\n\n const categoryOptions: Array<{ value: '' | SubProviderCategory; label: string }> = [\n { value: '', label: tr('providers.categories.all', 'All') },\n ...Object.entries(CATEGORY_KEY).map(([value, key]) => ({\n value: value as SubProviderCategory,\n label: tr(key, formatEnumLabel(value as SubProviderCategory)),\n })),\n ];\n\n const statusOptions: Array<{ value: '' | SubProviderStatus; label: string }> = [\n { value: '', label: tr('providers.filters.anyStatus', 'Any status') },\n ...Object.entries(STATUS_KEY).map(([value, key]) => ({\n value: value as SubProviderStatus,\n label: tr(key, formatEnumLabel(value as SubProviderStatus)),\n })),\n ];\n\n const { data, isLoading, isError, refetch, isFetching } = useProviders({\n search: search || undefined,\n category: category || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = data?.items ?? [];\n const hasMore = data?.pageInfo.hasMore ?? false;\n\n // Live summary strip — computed from the fetched page (never fabricated).\n const verifiedCount = items.filter((p) => p.status === 'VERIFIED').length;\n const healthScores = items.map((p) => p.vendorHealthScore).filter((s): s is number => s != null);\n const avgHealth = healthScores.length\n ? Math.round(healthScores.reduce((a, b) => a + b, 0) / healthScores.length)\n : null;\n\n const submitSearch = (e: React.FormEvent) => {\n e.preventDefault();\n setSearch(searchInput.trim());\n setPage(0);\n };\n\n const summary: Array<{ icon: typeof Store; label: string; value: string }> = [\n {\n icon: Store,\n label: tr('providers.summary.onThisPage', 'On this page'),\n value: String(items.length),\n },\n {\n icon: BadgeCheck,\n label: tr('providers.filters.verified', 'Verified'),\n value: String(verifiedCount),\n },\n {\n icon: Activity,\n label: tr('providers.summary.avgVendorHealth', 'Avg. vendor health'),\n value: avgHealth != null ? `${avgHealth}/100` : '—',\n },\n ];\n\n return (\n <PageLayout\n title={tr('providers.title', 'Discover providers')}\n description={tr(\n 'providers.description',\n 'Browse the global provider registry — the public face of the Subscription Knowledge Graph'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" /> {tr('providers.addProvider', 'Add provider')}\n </button>\n }\n >\n <PagePurpose className=\"mb-4\">\n {tr(\n 'providers.purpose',\n 'Every provider exists here whether or not it integrates. Search and filter to find the ones you have relationships with, compare their vendor health, or add a new one.'\n )}\n </PagePurpose>\n\n <EmphasisPanel>\n <form onSubmit={submitSearch} className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"text\"\n placeholder={tr('providers.searchPlaceholder', 'Search providers by name…')}\n value={searchInput}\n onChange={(e) => setSearchInput(e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2.5 pl-10 pr-24 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <button\n type=\"submit\"\n className=\"absolute right-1.5 top-1/2 -translate-y-1/2 rounded-md bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text hover:opacity-90\"\n >\n {tr('providers.search', 'Search')}\n </button>\n </form>\n </EmphasisPanel>\n\n {/* Category filter chips */}\n <div className=\"mt-4 flex items-center gap-2 overflow-x-auto pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\">\n {categoryOptions.map((o) => {\n const active = category === o.value;\n return (\n <button\n key={o.value || 'all'}\n type=\"button\"\n aria-pressed={active}\n onClick={() => {\n setCategory(o.value);\n setPage(0);\n }}\n className={cn(\n 'shrink-0 rounded-full border px-3.5 py-1.5 text-xs font-medium transition-colors',\n active\n ? 'border-transparent bg-action-primary-bg text-action-primary-text'\n : 'border-border-subtle bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n {o.label}\n </button>\n );\n })}\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-2\">\n <select\n aria-label={tr('providers.filters.statusLabel', 'Filter by status')}\n value={status}\n onChange={(e) => {\n setStatus(e.target.value as '' | SubProviderStatus);\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {statusOptions.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Live summary strip */}\n {!isLoading && !isError && items.length > 0 && (\n <div className=\"mt-4 grid grid-cols-1 gap-3 sm:grid-cols-3\">\n {summary.map((s) => (\n <GlassCard key={s.label} treatment=\"metric\" className=\"flex items-center gap-3 p-4\">\n <span className=\"flex size-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <s.icon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs text-text-secondary\">{s.label}</p>\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">{s.value}</p>\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n\n {isLoading ? (\n <LoadingView label={tr('providers.loading', 'Loading providers…')} />\n ) : isError ? (\n <ErrorView\n message={tr('providers.error', 'Could not load the provider registry.')}\n onRetry={() => void refetch()}\n />\n ) : items.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-search\"\n icon={<Building2 className=\"h-12 w-12\" />}\n title={tr('providers.emptyTitle', 'No providers found')}\n description={\n search\n ? tr('providers.emptyDescriptionSearch', 'No providers match \"{{search}}\".', {\n search,\n })\n : tr(\n 'providers.emptyDescription',\n 'No providers in the registry with these filters yet.'\n )\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers/new')}\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 {tr('providers.addProvider', 'Add provider')}\n </button>\n }\n />\n </div>\n ) : (\n <>\n <div className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {items.map((p) => (\n <ProviderCard\n key={p.id}\n provider={p}\n onOpen={() => navigateTo(`/providers/${p.id}`)}\n />\n ))}\n </div>\n\n <div className=\"mt-6 flex items-center justify-between\">\n <p className=\"text-xs text-text-secondary\">\n {tr('providers.pagination.page', 'Page {{page}}', { page: page + 1 })}\n {isFetching ? ` · ${tr('providers.pagination.refreshing', 'refreshing…')}` : ''}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n disabled={page === 0}\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n page === 0\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n {tr('providers.pagination.previous', 'Previous')}\n </button>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setPage((p) => p + 1)}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n !hasMore\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n {tr('providers.pagination.next', 'Next')}\n </button>\n </div>\n </div>\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|