@agent-native/core 0.123.0 → 0.123.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.
Files changed (263) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/templates-meta.ts +12 -0
  5. package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +115 -0
  6. package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +177 -0
  7. package/corpus/templates/crm/.agents/skills/crm/SKILL.md +205 -0
  8. package/corpus/templates/crm/.agents/skills/customizing-agent-native/SKILL.md +220 -0
  9. package/corpus/templates/crm/.agents/skills/feature-flags/SKILL.md +169 -0
  10. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +228 -0
  11. package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +178 -0
  12. package/corpus/templates/crm/.agents/skills/upgrade-agent-native/SKILL.md +122 -0
  13. package/corpus/templates/crm/.env.example +5 -0
  14. package/corpus/templates/crm/AGENTS.md +82 -0
  15. package/corpus/templates/crm/CHANGELOG.md +25 -0
  16. package/corpus/templates/crm/DEVELOPING.md +66 -0
  17. package/corpus/templates/crm/README.md +122 -0
  18. package/corpus/templates/crm/_gitignore +12 -0
  19. package/corpus/templates/crm/actions/_crm-action-utils.ts +98 -0
  20. package/corpus/templates/crm/actions/_crm-attribute-utils.ts +219 -0
  21. package/corpus/templates/crm/actions/_crm-dashboard.ts +69 -0
  22. package/corpus/templates/crm/actions/_crm-data-program-actions.ts +15 -0
  23. package/corpus/templates/crm/actions/_crm-list-utils.ts +775 -0
  24. package/corpus/templates/crm/actions/_crm-signal-utils.ts +79 -0
  25. package/corpus/templates/crm/actions/add-crm-record-to-list.ts +164 -0
  26. package/corpus/templates/crm/actions/apply-crm-proposals.ts +291 -0
  27. package/corpus/templates/crm/actions/archive-crm-attribute.ts +69 -0
  28. package/corpus/templates/crm/actions/attach-call-evidence.ts +170 -0
  29. package/corpus/templates/crm/actions/configure-crm-connection.ts +126 -0
  30. package/corpus/templates/crm/actions/configure-native-crm.ts +44 -0
  31. package/corpus/templates/crm/actions/create-crm-attribute.ts +350 -0
  32. package/corpus/templates/crm/actions/create-crm-list.ts +194 -0
  33. package/corpus/templates/crm/actions/create-crm-record.ts +148 -0
  34. package/corpus/templates/crm/actions/create-crm-signal-tracker.ts +58 -0
  35. package/corpus/templates/crm/actions/delete-crm-saved-view.ts +68 -0
  36. package/corpus/templates/crm/actions/delete-staged-dataset.ts +14 -0
  37. package/corpus/templates/crm/actions/estimate-crm-enrichment.ts +229 -0
  38. package/corpus/templates/crm/actions/find-crm-duplicates.ts +87 -0
  39. package/corpus/templates/crm/actions/get-crm-automation-recipe.ts +36 -0
  40. package/corpus/templates/crm/actions/get-crm-dashboard-panel.ts +35 -0
  41. package/corpus/templates/crm/actions/get-crm-dashboard.ts +14 -0
  42. package/corpus/templates/crm/actions/get-crm-overview.ts +13 -0
  43. package/corpus/templates/crm/actions/get-crm-pipeline-data.ts +61 -0
  44. package/corpus/templates/crm/actions/get-crm-record-page.ts +287 -0
  45. package/corpus/templates/crm/actions/get-crm-record.ts +75 -0
  46. package/corpus/templates/crm/actions/get-crm-workspace.ts +364 -0
  47. package/corpus/templates/crm/actions/install-crm-pipeline-dashboard.ts +81 -0
  48. package/corpus/templates/crm/actions/list-crm-attributes.ts +71 -0
  49. package/corpus/templates/crm/actions/list-crm-connections.ts +68 -0
  50. package/corpus/templates/crm/actions/list-crm-dashboard-revisions.ts +15 -0
  51. package/corpus/templates/crm/actions/list-crm-dashboards.ts +15 -0
  52. package/corpus/templates/crm/actions/list-crm-enrichment-slots.ts +28 -0
  53. package/corpus/templates/crm/actions/list-crm-list-entries.ts +192 -0
  54. package/corpus/templates/crm/actions/list-crm-lists.ts +80 -0
  55. package/corpus/templates/crm/actions/list-crm-proposals.ts +55 -0
  56. package/corpus/templates/crm/actions/list-crm-record-field-history.ts +201 -0
  57. package/corpus/templates/crm/actions/list-crm-record-values.ts +200 -0
  58. package/corpus/templates/crm/actions/list-crm-records.ts +103 -0
  59. package/corpus/templates/crm/actions/list-crm-saved-views.ts +66 -0
  60. package/corpus/templates/crm/actions/list-crm-signal-hits.ts +43 -0
  61. package/corpus/templates/crm/actions/list-crm-signal-trackers.ts +32 -0
  62. package/corpus/templates/crm/actions/list-crm-tasks.ts +22 -0
  63. package/corpus/templates/crm/actions/list-staged-datasets.ts +14 -0
  64. package/corpus/templates/crm/actions/list-workspace-connections.ts +47 -0
  65. package/corpus/templates/crm/actions/manage-crm-attribute-option.ts +272 -0
  66. package/corpus/templates/crm/actions/manage-crm-signal-tracker.ts +87 -0
  67. package/corpus/templates/crm/actions/manage-crm-task.ts +132 -0
  68. package/corpus/templates/crm/actions/merge-crm-records.ts +691 -0
  69. package/corpus/templates/crm/actions/navigate.ts +54 -0
  70. package/corpus/templates/crm/actions/provider-api-catalog.ts +22 -0
  71. package/corpus/templates/crm/actions/provider-api-docs.ts +54 -0
  72. package/corpus/templates/crm/actions/provider-api-request.ts +92 -0
  73. package/corpus/templates/crm/actions/query-staged-dataset.ts +44 -0
  74. package/corpus/templates/crm/actions/record-crm-call-insight.ts +160 -0
  75. package/corpus/templates/crm/actions/record-crm-smart-signal.ts +150 -0
  76. package/corpus/templates/crm/actions/remove-crm-list-entry.ts +79 -0
  77. package/corpus/templates/crm/actions/restore-crm-dashboard-revision.ts +23 -0
  78. package/corpus/templates/crm/actions/review-crm-signal.ts +31 -0
  79. package/corpus/templates/crm/actions/run-crm-attribute-fill.ts +443 -0
  80. package/corpus/templates/crm/actions/run-crm-enrichment.ts +647 -0
  81. package/corpus/templates/crm/actions/run-crm-saved-view-program.ts +103 -0
  82. package/corpus/templates/crm/actions/run-crm-signal-trackers.ts +265 -0
  83. package/corpus/templates/crm/actions/run.ts +7 -0
  84. package/corpus/templates/crm/actions/save-crm-dashboard.ts +51 -0
  85. package/corpus/templates/crm/actions/save-crm-saved-view.ts +296 -0
  86. package/corpus/templates/crm/actions/sync-crm.ts +146 -0
  87. package/corpus/templates/crm/actions/update-crm-attribute.ts +111 -0
  88. package/corpus/templates/crm/actions/update-crm-list-entry.ts +102 -0
  89. package/corpus/templates/crm/actions/update-crm-list.ts +97 -0
  90. package/corpus/templates/crm/actions/update-crm-record.ts +543 -0
  91. package/corpus/templates/crm/actions/view-screen.ts +324 -0
  92. package/corpus/templates/crm/agent-native.app-skill.json +69 -0
  93. package/corpus/templates/crm/app/components/crm/CreateCrmRecordDialog.tsx +158 -0
  94. package/corpus/templates/crm/app/components/crm/CrmDashboardPanel.tsx +78 -0
  95. package/corpus/templates/crm/app/components/crm/CrmSignalsPanel.tsx +220 -0
  96. package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +414 -0
  97. package/corpus/templates/crm/app/components/crm/RecordActions.tsx +648 -0
  98. package/corpus/templates/crm/app/components/crm/RecordGrid.tsx +488 -0
  99. package/corpus/templates/crm/app/components/crm/RecordWorkspace.tsx +232 -0
  100. package/corpus/templates/crm/app/components/crm/SavedViewDataProgram.tsx +93 -0
  101. package/corpus/templates/crm/app/components/crm/Surface.tsx +97 -0
  102. package/corpus/templates/crm/app/components/crm/WorkOverview.tsx +180 -0
  103. package/corpus/templates/crm/app/components/crm/board/CrmBoard.tsx +988 -0
  104. package/corpus/templates/crm/app/components/crm/board/board-model.ts +399 -0
  105. package/corpus/templates/crm/app/components/crm/board/entry-filter.ts +150 -0
  106. package/corpus/templates/crm/app/components/crm/board/view-draft.ts +227 -0
  107. package/corpus/templates/crm/app/components/crm/grid/CrmGrid.tsx +1033 -0
  108. package/corpus/templates/crm/app/components/crm/grid/GridCell.tsx +620 -0
  109. package/corpus/templates/crm/app/components/crm/grid/clipboard.ts +84 -0
  110. package/corpus/templates/crm/app/components/crm/grid/model.ts +304 -0
  111. package/corpus/templates/crm/app/components/crm/grid/navigation.ts +220 -0
  112. package/corpus/templates/crm/app/components/crm/grid/query.ts +214 -0
  113. package/corpus/templates/crm/app/components/crm/record/AttributePanel.tsx +192 -0
  114. package/corpus/templates/crm/app/components/crm/record/FieldHistory.tsx +179 -0
  115. package/corpus/templates/crm/app/components/crm/record/RecordHeader.tsx +103 -0
  116. package/corpus/templates/crm/app/components/crm/record/RecordLists.tsx +235 -0
  117. package/corpus/templates/crm/app/components/crm/record/RecordTabs.tsx +206 -0
  118. package/corpus/templates/crm/app/components/crm/record/field-editors.tsx +245 -0
  119. package/corpus/templates/crm/app/components/crm/record/record-data.ts +603 -0
  120. package/corpus/templates/crm/app/components/crm/settings/AdvancedSettings.tsx +49 -0
  121. package/corpus/templates/crm/app/components/crm/settings/AttributeOptionsEditor.tsx +441 -0
  122. package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +169 -0
  123. package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +935 -0
  124. package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +339 -0
  125. package/corpus/templates/crm/app/components/crm/settings/settings-admin.ts +377 -0
  126. package/corpus/templates/crm/app/components/crm/shared/AttributeValueParts.tsx +69 -0
  127. package/corpus/templates/crm/app/components/crm/shared/attribute-value.ts +591 -0
  128. package/corpus/templates/crm/app/components/layout/CrmCommandMenu.tsx +556 -0
  129. package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +130 -0
  130. package/corpus/templates/crm/app/components/layout/CrmSidebar.tsx +407 -0
  131. package/corpus/templates/crm/app/components/layout/command-palette-search.ts +126 -0
  132. package/corpus/templates/crm/app/components/layout/crm-ui-intents.ts +41 -0
  133. package/corpus/templates/crm/app/components/layout/sidebar-collapse.ts +40 -0
  134. package/corpus/templates/crm/app/components/layout/sidebar-lists.ts +109 -0
  135. package/corpus/templates/crm/app/components/ui/alert-dialog.tsx +1 -0
  136. package/corpus/templates/crm/app/components/ui/badge.tsx +1 -0
  137. package/corpus/templates/crm/app/components/ui/button.tsx +1 -0
  138. package/corpus/templates/crm/app/components/ui/card.tsx +1 -0
  139. package/corpus/templates/crm/app/components/ui/checkbox.tsx +1 -0
  140. package/corpus/templates/crm/app/components/ui/dialog.tsx +1 -0
  141. package/corpus/templates/crm/app/components/ui/dropdown-menu.tsx +1 -0
  142. package/corpus/templates/crm/app/components/ui/hover-card.tsx +1 -0
  143. package/corpus/templates/crm/app/components/ui/input.tsx +1 -0
  144. package/corpus/templates/crm/app/components/ui/label.tsx +1 -0
  145. package/corpus/templates/crm/app/components/ui/popover.tsx +1 -0
  146. package/corpus/templates/crm/app/components/ui/select.tsx +1 -0
  147. package/corpus/templates/crm/app/components/ui/sheet.tsx +1 -0
  148. package/corpus/templates/crm/app/components/ui/skeleton.tsx +1 -0
  149. package/corpus/templates/crm/app/components/ui/switch.tsx +1 -0
  150. package/corpus/templates/crm/app/components/ui/table.tsx +1 -0
  151. package/corpus/templates/crm/app/components/ui/tabs.tsx +1 -0
  152. package/corpus/templates/crm/app/components/ui/textarea.tsx +1 -0
  153. package/corpus/templates/crm/app/components/ui/toolkit-provider.tsx +17 -0
  154. package/corpus/templates/crm/app/components/ui/tooltip.tsx +1 -0
  155. package/corpus/templates/crm/app/design-system.ts +3 -0
  156. package/corpus/templates/crm/app/entry.client.tsx +19 -0
  157. package/corpus/templates/crm/app/entry.server.tsx +10 -0
  158. package/corpus/templates/crm/app/global.css +167 -0
  159. package/corpus/templates/crm/app/hooks/use-keyboard-shortcuts.ts +206 -0
  160. package/corpus/templates/crm/app/hooks/use-navigation-state.ts +48 -0
  161. package/corpus/templates/crm/app/i18n/en-US.ts +684 -0
  162. package/corpus/templates/crm/app/i18n/index.ts +34 -0
  163. package/corpus/templates/crm/app/lib/dashboard.ts +13 -0
  164. package/corpus/templates/crm/app/lib/navigation.ts +17 -0
  165. package/corpus/templates/crm/app/lib/tab-id.ts +1 -0
  166. package/corpus/templates/crm/app/lib/types.ts +172 -0
  167. package/corpus/templates/crm/app/lib/utils.ts +1 -0
  168. package/corpus/templates/crm/app/root.tsx +93 -0
  169. package/corpus/templates/crm/app/routes/_index.tsx +26 -0
  170. package/corpus/templates/crm/app/routes/accounts.tsx +41 -0
  171. package/corpus/templates/crm/app/routes/agent.tsx +5 -0
  172. package/corpus/templates/crm/app/routes/ask.tsx +53 -0
  173. package/corpus/templates/crm/app/routes/dashboard.tsx +118 -0
  174. package/corpus/templates/crm/app/routes/lists.tsx +228 -0
  175. package/corpus/templates/crm/app/routes/opportunities.tsx +41 -0
  176. package/corpus/templates/crm/app/routes/people.tsx +41 -0
  177. package/corpus/templates/crm/app/routes/proposals.tsx +388 -0
  178. package/corpus/templates/crm/app/routes/records.$recordId.tsx +53 -0
  179. package/corpus/templates/crm/app/routes/records.tsx +64 -0
  180. package/corpus/templates/crm/app/routes/settings.tsx +118 -0
  181. package/corpus/templates/crm/app/routes/setup.tsx +308 -0
  182. package/corpus/templates/crm/app/routes/tasks.tsx +252 -0
  183. package/corpus/templates/crm/app/routes/views.tsx +683 -0
  184. package/corpus/templates/crm/app/routes.ts +19 -0
  185. package/corpus/templates/crm/app/vite-env.d.ts +6 -0
  186. package/corpus/templates/crm/changelog/2026-07-26-any-list-or-view-can-be-shown-as-a-board-grouped-by-stage-so.md +6 -0
  187. package/corpus/templates/crm/changelog/2026-07-26-crm-now-finds-likely-duplicate-records-and-tells-you-exactly.md +6 -0
  188. package/corpus/templates/crm/changelog/2026-07-26-enrichment-now-shows-what-a-run-will-cost-before-it-starts-g.md +6 -0
  189. package/corpus/templates/crm/changelog/2026-07-26-lists-turn-any-set-of-records-into-a-pipeline-with-its-own-s.md +6 -0
  190. package/corpus/templates/crm/changelog/2026-07-26-moving-a-record-or-list-entry-into-a-stage-that-was-retired-.md +6 -0
  191. package/corpus/templates/crm/changelog/2026-07-26-opening-a-record-now-shows-one-page-with-its-fields-the-list.md +6 -0
  192. package/corpus/templates/crm/changelog/2026-07-26-press-cmd-k-to-jump-to-any-record-list-view-or-setting-and-u.md +6 -0
  193. package/corpus/templates/crm/changelog/2026-07-26-provider-record-edits-now-show-an-exact-before-after-diff-an.md +6 -0
  194. package/corpus/templates/crm/changelog/2026-07-26-record-page-attribute-labels-now-read-in-full-instead-of-bei.md +6 -0
  195. package/corpus/templates/crm/changelog/2026-07-26-records-now-use-typed-fields-currency-dates-ratings-status-s.md +6 -0
  196. package/corpus/templates/crm/changelog/2026-07-26-retyping-a-value-on-the-record-page-now-replaces-it-instead-.md +6 -0
  197. package/corpus/templates/crm/changelog/2026-07-26-settings-now-has-fields-and-lists-tabs-for-adding-fields-edi.md +6 -0
  198. package/corpus/templates/crm/changelog/2026-07-26-the-proposal-review-now-shows-the-field-that-actually-change.md +6 -0
  199. package/corpus/templates/crm/changelog/2026-07-26-the-record-grid-is-now-a-real-spreadsheet-filter-sort-and-pa.md +6 -0
  200. package/corpus/templates/crm/components.json +20 -0
  201. package/corpus/templates/crm/docs/architecture/crm-contract.md +214 -0
  202. package/corpus/templates/crm/docs/features/README.md +55 -0
  203. package/corpus/templates/crm/docs/features/f1-records-and-fields.mdx +52 -0
  204. package/corpus/templates/crm/docs/features/f2-lists-pipelines-and-views.mdx +48 -0
  205. package/corpus/templates/crm/docs/features/f3-work-and-record-quality.mdx +40 -0
  206. package/corpus/templates/crm/docs/features/f4-evidence-and-chat.mdx +47 -0
  207. package/corpus/templates/crm/learnings.defaults.md +5 -0
  208. package/corpus/templates/crm/netlify.toml +11 -0
  209. package/corpus/templates/crm/package.json +61 -0
  210. package/corpus/templates/crm/public/favicon.svg +1 -0
  211. package/corpus/templates/crm/public/icon-180.svg +1 -0
  212. package/corpus/templates/crm/public/manifest.json +11 -0
  213. package/corpus/templates/crm/react-router.config.ts +7 -0
  214. package/corpus/templates/crm/server/crm/adapter.ts +32 -0
  215. package/corpus/templates/crm/server/crm/crm-field-firewall.ts +64 -0
  216. package/corpus/templates/crm/server/crm/crm-mirror.ts +721 -0
  217. package/corpus/templates/crm/server/crm/field-policy-attributes.ts +48 -0
  218. package/corpus/templates/crm/server/crm/hubspot-adapter.ts +1146 -0
  219. package/corpus/templates/crm/server/crm/native-adapter.ts +1810 -0
  220. package/corpus/templates/crm/server/crm/provider-record-link.ts +264 -0
  221. package/corpus/templates/crm/server/crm/read-through.ts +207 -0
  222. package/corpus/templates/crm/server/crm/salesforce-adapter.ts +1255 -0
  223. package/corpus/templates/crm/server/db/crm-store.ts +1112 -0
  224. package/corpus/templates/crm/server/db/index.ts +191 -0
  225. package/corpus/templates/crm/server/db/schema.ts +647 -0
  226. package/corpus/templates/crm/server/lib/crm-query.ts +1558 -0
  227. package/corpus/templates/crm/server/lib/dedupe.ts +714 -0
  228. package/corpus/templates/crm/server/lib/enrichment-cost.ts +381 -0
  229. package/corpus/templates/crm/server/lib/enrichment-slots.ts +519 -0
  230. package/corpus/templates/crm/server/lib/intelligence/default-detectors.ts +65 -0
  231. package/corpus/templates/crm/server/lib/intelligence/evidence.ts +105 -0
  232. package/corpus/templates/crm/server/lib/intelligence/keyword-detector.ts +94 -0
  233. package/corpus/templates/crm/server/lib/intelligence/smart-detector.ts +39 -0
  234. package/corpus/templates/crm/server/lib/intelligence/summary.ts +19 -0
  235. package/corpus/templates/crm/server/lib/lifecycle.ts +634 -0
  236. package/corpus/templates/crm/server/lib/provider-api.ts +64 -0
  237. package/corpus/templates/crm/server/middleware/auth.ts +4 -0
  238. package/corpus/templates/crm/server/plugins/agent-chat.ts +65 -0
  239. package/corpus/templates/crm/server/plugins/auth.ts +14 -0
  240. package/corpus/templates/crm/server/plugins/core-routes.ts +17 -0
  241. package/corpus/templates/crm/server/plugins/db.ts +738 -0
  242. package/corpus/templates/crm/server/routes/[...page].get.ts +5 -0
  243. package/corpus/templates/crm/shared/crm-attributes.ts +469 -0
  244. package/corpus/templates/crm/shared/crm-automation-recipes.ts +132 -0
  245. package/corpus/templates/crm/shared/crm-contract.ts +417 -0
  246. package/corpus/templates/crm/shared/crm-navigation.ts +190 -0
  247. package/corpus/templates/crm/shared/crm-sales-config.ts +63 -0
  248. package/corpus/templates/crm/ssr-entry.ts +11 -0
  249. package/corpus/templates/crm/tsconfig.json +24 -0
  250. package/corpus/templates/crm/vite.config.ts +15 -0
  251. package/corpus/templates/crm/vitest.config.ts +5 -0
  252. package/corpus/templates/design/app/i18n-data.ts +12 -12
  253. package/corpus/templates/design/changelog/2026-07-26-apply-design-updates-from-a-centered-top-toolbar-without-cov.md +6 -0
  254. package/dist/cli/templates-meta.d.ts.map +1 -1
  255. package/dist/cli/templates-meta.js +12 -0
  256. package/dist/cli/templates-meta.js.map +1 -1
  257. package/dist/collab/routes.d.ts +1 -1
  258. package/dist/observability/routes.d.ts +3 -3
  259. package/dist/resources/handlers.d.ts +1 -1
  260. package/dist/server/realtime-token.d.ts +1 -1
  261. package/dist/server/transcribe-voice.d.ts +1 -1
  262. package/package.json +1 -1
  263. package/src/cli/templates-meta.ts +12 -0
@@ -0,0 +1,441 @@
1
+ import { useActionMutation } from "@agent-native/core/client/hooks";
2
+ import { useT } from "@agent-native/core/client/i18n";
3
+ import {
4
+ IconArchive,
5
+ IconArrowBackUp,
6
+ IconChevronDown,
7
+ IconChevronUp,
8
+ IconGripVertical,
9
+ IconPlus,
10
+ } from "@tabler/icons-react";
11
+ import { useState, type ReactNode } from "react";
12
+ import { toast } from "sonner";
13
+
14
+ import {
15
+ AlertDialog,
16
+ AlertDialogAction,
17
+ AlertDialogCancel,
18
+ AlertDialogContent,
19
+ AlertDialogDescription,
20
+ AlertDialogFooter,
21
+ AlertDialogHeader,
22
+ AlertDialogTitle,
23
+ AlertDialogTrigger,
24
+ } from "@/components/ui/alert-dialog";
25
+ import { Badge } from "@/components/ui/badge";
26
+ import { Button } from "@/components/ui/button";
27
+ import {
28
+ Dialog,
29
+ DialogContent,
30
+ DialogDescription,
31
+ DialogHeader,
32
+ DialogTitle,
33
+ DialogTrigger,
34
+ } from "@/components/ui/dialog";
35
+ import { Input } from "@/components/ui/input";
36
+ import { Label } from "@/components/ui/label";
37
+ import {
38
+ Select,
39
+ SelectContent,
40
+ SelectItem,
41
+ SelectTrigger,
42
+ SelectValue,
43
+ } from "@/components/ui/select";
44
+ import { Switch } from "@/components/ui/switch";
45
+
46
+ import type {
47
+ CrmAttributeDefinition,
48
+ CrmAttributeOption,
49
+ } from "../../../../shared/crm-contract";
50
+ import {
51
+ attributeTypeCapabilities,
52
+ moveItem,
53
+ reorderedOptionIds,
54
+ } from "./settings-admin";
55
+
56
+ /** Option colors offered by the picker, with the swatch each one renders as. */
57
+ const OPTION_COLORS: Array<{ value: string; swatch: string }> = [
58
+ { value: "gray", swatch: "bg-muted-foreground/50" },
59
+ { value: "blue", swatch: "bg-sky-500" },
60
+ { value: "green", swatch: "bg-emerald-500" },
61
+ { value: "amber", swatch: "bg-amber-500" },
62
+ { value: "red", swatch: "bg-rose-500" },
63
+ { value: "purple", swatch: "bg-violet-500" },
64
+ ];
65
+
66
+ const NO_COLOR = "__none__";
67
+
68
+ interface ManageOptionInput {
69
+ attributeId: string;
70
+ operation: "add" | "update" | "archive" | "reorder";
71
+ optionId?: string;
72
+ value?: string;
73
+ title?: string;
74
+ color?: string | null;
75
+ targetDays?: number | null;
76
+ celebrate?: boolean;
77
+ archived?: boolean;
78
+ optionIds?: string[];
79
+ }
80
+
81
+ type OptionUpdateInput = Omit<
82
+ ManageOptionInput,
83
+ "attributeId" | "operation" | "optionId"
84
+ >;
85
+
86
+ export function AttributeOptionsEditor({
87
+ attribute,
88
+ patchAttribute,
89
+ trigger,
90
+ }: {
91
+ attribute: CrmAttributeDefinition;
92
+ patchAttribute: (
93
+ attributeId: string,
94
+ patch: Partial<CrmAttributeDefinition>,
95
+ ) => () => void;
96
+ trigger: ReactNode;
97
+ }) {
98
+ const t = useT();
99
+ const [open, setOpen] = useState(false);
100
+ const [newValue, setNewValue] = useState("");
101
+ const [dragIndex, setDragIndex] = useState<number | null>(null);
102
+ const manage = useActionMutation<
103
+ { options?: CrmAttributeOption[] },
104
+ ManageOptionInput
105
+ >("manage-crm-attribute-option" as never);
106
+ const showStageFields = attributeTypeCapabilities(
107
+ attribute.attributeType,
108
+ ).showsStageFields;
109
+ const options = attribute.options ?? [];
110
+
111
+ /**
112
+ * Every option write goes through here so the optimistic options and the
113
+ * rollback stay in one place: a half-applied reorder is worse than no
114
+ * optimism at all.
115
+ */
116
+ async function apply(input: ManageOptionInput, next: CrmAttributeOption[]) {
117
+ const rollback = patchAttribute(attribute.id, { options: next });
118
+ try {
119
+ const result = await manage.mutateAsync(input);
120
+ if (result?.options) {
121
+ patchAttribute(attribute.id, { options: result.options });
122
+ }
123
+ } catch (error) {
124
+ rollback();
125
+ toast.error(
126
+ error instanceof Error ? error.message : t("fields.optionFailed"),
127
+ );
128
+ }
129
+ }
130
+
131
+ function move(from: number, to: number) {
132
+ if (to < 0 || to >= options.length || from === to) return;
133
+ const next = moveItem(options, from, to).map((option, position) => ({
134
+ ...option,
135
+ position,
136
+ }));
137
+ void apply(
138
+ {
139
+ attributeId: attribute.id,
140
+ operation: "reorder",
141
+ optionIds: reorderedOptionIds(options, from, to),
142
+ },
143
+ next,
144
+ );
145
+ }
146
+
147
+ function patchOption(
148
+ optionId: string,
149
+ patch: Partial<CrmAttributeOption>,
150
+ input: OptionUpdateInput,
151
+ ) {
152
+ void apply(
153
+ {
154
+ attributeId: attribute.id,
155
+ operation: "update",
156
+ optionId,
157
+ ...input,
158
+ },
159
+ options.map((option) =>
160
+ option.id === optionId ? { ...option, ...patch } : option,
161
+ ),
162
+ );
163
+ }
164
+
165
+ async function addOption() {
166
+ const value = newValue.trim();
167
+ if (!value) return;
168
+ setNewValue("");
169
+ await apply(
170
+ { attributeId: attribute.id, operation: "add", value, title: value },
171
+ [
172
+ ...options,
173
+ {
174
+ id: `pending-${value}`,
175
+ value,
176
+ title: value,
177
+ position: options.length,
178
+ archived: false,
179
+ },
180
+ ],
181
+ );
182
+ }
183
+
184
+ return (
185
+ <Dialog open={open} onOpenChange={setOpen}>
186
+ <DialogTrigger asChild>{trigger}</DialogTrigger>
187
+ <DialogContent className="sm:max-w-2xl">
188
+ <DialogHeader>
189
+ <DialogTitle>
190
+ {t("fields.optionsTitle", { name: attribute.label })}
191
+ </DialogTitle>
192
+ <DialogDescription>
193
+ {showStageFields
194
+ ? t("fields.optionsStageDescription")
195
+ : t("fields.optionsDescription")}
196
+ </DialogDescription>
197
+ </DialogHeader>
198
+
199
+ <div className="grid gap-2">
200
+ {options.map((option, index) => (
201
+ <div
202
+ key={option.id}
203
+ draggable
204
+ onDragStart={() => setDragIndex(index)}
205
+ onDragOver={(event) => event.preventDefault()}
206
+ onDrop={() => {
207
+ if (dragIndex !== null) move(dragIndex, index);
208
+ setDragIndex(null);
209
+ }}
210
+ onDragEnd={() => setDragIndex(null)}
211
+ className={`flex flex-wrap items-center gap-2 rounded-md border border-border/70 bg-card px-2 py-2 ${
212
+ option.archived ? "opacity-60" : ""
213
+ }`}
214
+ >
215
+ <IconGripVertical
216
+ aria-hidden="true"
217
+ className="size-4 shrink-0 cursor-grab text-muted-foreground"
218
+ />
219
+ <div className="flex shrink-0 flex-col">
220
+ <Button
221
+ variant="ghost"
222
+ size="icon"
223
+ className="size-5"
224
+ aria-label={t("fields.moveOptionUp", { name: option.title })}
225
+ disabled={index === 0}
226
+ onClick={() => move(index, index - 1)}
227
+ >
228
+ <IconChevronUp className="size-3.5" />
229
+ </Button>
230
+ <Button
231
+ variant="ghost"
232
+ size="icon"
233
+ className="size-5"
234
+ aria-label={t("fields.moveOptionDown", {
235
+ name: option.title,
236
+ })}
237
+ disabled={index === options.length - 1}
238
+ onClick={() => move(index, index + 1)}
239
+ >
240
+ <IconChevronDown className="size-3.5" />
241
+ </Button>
242
+ </div>
243
+ <Input
244
+ className="min-w-32 flex-1"
245
+ defaultValue={option.title}
246
+ maxLength={200}
247
+ aria-label={t("fields.optionTitleAria", {
248
+ value: option.value,
249
+ })}
250
+ onBlur={(event) => {
251
+ const title = event.target.value.trim();
252
+ if (!title || title === option.title) return;
253
+ patchOption(option.id, { title }, { title });
254
+ }}
255
+ />
256
+ <code className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">
257
+ {option.value}
258
+ </code>
259
+ <Select
260
+ value={option.color ?? NO_COLOR}
261
+ onValueChange={(color) =>
262
+ patchOption(
263
+ option.id,
264
+ { color: color === NO_COLOR ? undefined : color },
265
+ { color: color === NO_COLOR ? null : color },
266
+ )
267
+ }
268
+ >
269
+ <SelectTrigger
270
+ className="w-32"
271
+ aria-label={t("fields.optionColorAria", {
272
+ name: option.title,
273
+ })}
274
+ >
275
+ <SelectValue />
276
+ </SelectTrigger>
277
+ <SelectContent>
278
+ <SelectItem value={NO_COLOR}>
279
+ {t("fields.colorNone")}
280
+ </SelectItem>
281
+ {OPTION_COLORS.map((color) => (
282
+ <SelectItem key={color.value} value={color.value}>
283
+ <span className="flex items-center gap-2">
284
+ <span
285
+ className={`size-2.5 rounded-full ${color.swatch}`}
286
+ />
287
+ {color.value}
288
+ </span>
289
+ </SelectItem>
290
+ ))}
291
+ </SelectContent>
292
+ </Select>
293
+ {showStageFields ? (
294
+ <>
295
+ <Input
296
+ className="w-24"
297
+ type="number"
298
+ min={0}
299
+ defaultValue={option.targetDays ?? ""}
300
+ aria-label={t("fields.targetDaysAria", {
301
+ name: option.title,
302
+ })}
303
+ placeholder={t("fields.targetDays")}
304
+ onBlur={(event) => {
305
+ const raw = event.target.value.trim();
306
+ const targetDays = raw ? Number(raw) : null;
307
+ if (targetDays === (option.targetDays ?? null)) return;
308
+ patchOption(option.id, { targetDays }, { targetDays });
309
+ }}
310
+ />
311
+ <label className="flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground">
312
+ <Switch
313
+ checked={option.celebrate ?? false}
314
+ aria-label={t("fields.celebrateAria", {
315
+ name: option.title,
316
+ })}
317
+ onCheckedChange={(celebrate) =>
318
+ patchOption(option.id, { celebrate }, { celebrate })
319
+ }
320
+ />
321
+ {t("fields.celebrate")}
322
+ </label>
323
+ </>
324
+ ) : null}
325
+ {option.archived ? (
326
+ <Badge variant="outline" className="font-normal">
327
+ {t("fields.archived")}
328
+ </Badge>
329
+ ) : null}
330
+ {option.archived ? (
331
+ <Button
332
+ variant="ghost"
333
+ size="icon"
334
+ aria-label={t("fields.restoreOptionAria", {
335
+ name: option.title,
336
+ })}
337
+ onClick={() =>
338
+ patchOption(
339
+ option.id,
340
+ { archived: false },
341
+ { archived: false },
342
+ )
343
+ }
344
+ >
345
+ <IconArrowBackUp className="size-4" />
346
+ </Button>
347
+ ) : (
348
+ <ArchiveOptionButton
349
+ option={option}
350
+ onArchive={() =>
351
+ void apply(
352
+ {
353
+ attributeId: attribute.id,
354
+ operation: "archive",
355
+ optionId: option.id,
356
+ archived: true,
357
+ },
358
+ options.map((current) =>
359
+ current.id === option.id
360
+ ? { ...current, archived: true }
361
+ : current,
362
+ ),
363
+ )
364
+ }
365
+ />
366
+ )}
367
+ </div>
368
+ ))}
369
+ {options.length === 0 ? (
370
+ <p className="rounded-md border border-dashed border-border px-3 py-6 text-center text-sm text-muted-foreground">
371
+ {t("fields.optionsEmpty")}
372
+ </p>
373
+ ) : null}
374
+ </div>
375
+
376
+ <div className="grid gap-2 border-t border-border/70 pt-4">
377
+ <Label htmlFor="new-option-value">{t("fields.addOption")}</Label>
378
+ <div className="flex flex-wrap items-center gap-2">
379
+ <Input
380
+ id="new-option-value"
381
+ className="flex-1"
382
+ value={newValue}
383
+ maxLength={200}
384
+ placeholder={t("fields.optionValuePlaceholder")}
385
+ onChange={(event) => setNewValue(event.target.value)}
386
+ />
387
+ <Button
388
+ className="gap-1.5"
389
+ disabled={!newValue.trim()}
390
+ onClick={() => void addOption()}
391
+ >
392
+ <IconPlus className="size-4" /> {t("fields.addOption")}
393
+ </Button>
394
+ </div>
395
+ <p className="text-xs text-muted-foreground">
396
+ {t("fields.optionValueImmutable")}
397
+ </p>
398
+ </div>
399
+ </DialogContent>
400
+ </Dialog>
401
+ );
402
+ }
403
+
404
+ function ArchiveOptionButton({
405
+ option,
406
+ onArchive,
407
+ }: {
408
+ option: CrmAttributeOption;
409
+ onArchive: () => void;
410
+ }) {
411
+ const t = useT();
412
+ return (
413
+ <AlertDialog>
414
+ <AlertDialogTrigger asChild>
415
+ <Button
416
+ variant="ghost"
417
+ size="icon"
418
+ aria-label={t("fields.archiveOptionAria", { name: option.title })}
419
+ >
420
+ <IconArchive className="size-4" />
421
+ </Button>
422
+ </AlertDialogTrigger>
423
+ <AlertDialogContent>
424
+ <AlertDialogHeader>
425
+ <AlertDialogTitle>
426
+ {t("fields.archiveOptionTitle", { name: option.title })}
427
+ </AlertDialogTitle>
428
+ <AlertDialogDescription>
429
+ {t("fields.archiveOptionDescription")}
430
+ </AlertDialogDescription>
431
+ </AlertDialogHeader>
432
+ <AlertDialogFooter>
433
+ <AlertDialogCancel>{t("fields.cancel")}</AlertDialogCancel>
434
+ <AlertDialogAction onClick={onArchive}>
435
+ {t("fields.archive")}
436
+ </AlertDialogAction>
437
+ </AlertDialogFooter>
438
+ </AlertDialogContent>
439
+ </AlertDialog>
440
+ );
441
+ }
@@ -0,0 +1,169 @@
1
+ import { useActionQuery } from "@agent-native/core/client/hooks";
2
+ import { useT } from "@agent-native/core/client/i18n";
3
+ import { IconAlertTriangle, IconPlugConnected } from "@tabler/icons-react";
4
+ import { Link } from "react-router";
5
+
6
+ import { Badge } from "@/components/ui/badge";
7
+ import { Button } from "@/components/ui/button";
8
+
9
+ import {
10
+ connectionModeInfo,
11
+ CRM_CONNECTION_MODE_INFO,
12
+ SELECTABLE_CRM_CONNECTION_MODES,
13
+ } from "./settings-admin";
14
+
15
+ interface CrmConnectionSummary {
16
+ id: string;
17
+ provider: string;
18
+ label: string;
19
+ accountId: string | null;
20
+ mode: string;
21
+ status: string;
22
+ objectTypes: string[];
23
+ lastSyncedAt: string | null;
24
+ lastError: string | null;
25
+ }
26
+
27
+ export function ConnectionSettings() {
28
+ const t = useT();
29
+ const connectionsQuery = useActionQuery<{
30
+ connections: CrmConnectionSummary[];
31
+ }>("list-crm-connections" as never, {} as never);
32
+ const connections = connectionsQuery.data?.connections ?? [];
33
+ const hasDeprecatedMode = connections.some(
34
+ (connection) => connectionModeInfo(connection.mode)?.deprecated,
35
+ );
36
+
37
+ return (
38
+ <div className="mx-auto w-full max-w-2xl">
39
+ <h1 className="text-xl font-semibold tracking-tight">
40
+ {t("connection.title")}
41
+ </h1>
42
+ <p className="mt-1 text-sm leading-6 text-muted-foreground">
43
+ {t("connection.description")}
44
+ </p>
45
+
46
+ <div className="mt-5 grid gap-3 rounded-lg border border-border/70 bg-card p-4">
47
+ <p className="text-sm font-medium">{t("connection.modesTitle")}</p>
48
+ {SELECTABLE_CRM_CONNECTION_MODES.map((mode) => (
49
+ <div key={mode} className="grid gap-0.5">
50
+ <p className="text-sm">
51
+ {t(CRM_CONNECTION_MODE_INFO[mode].labelKey)}
52
+ </p>
53
+ <p className="text-xs leading-5 text-muted-foreground">
54
+ {t(CRM_CONNECTION_MODE_INFO[mode].descriptionKey)}
55
+ </p>
56
+ </div>
57
+ ))}
58
+ </div>
59
+
60
+ {hasDeprecatedMode ? (
61
+ <div className="mt-4 flex items-start gap-3 rounded-lg border border-border/70 bg-muted/40 px-4 py-3">
62
+ <IconAlertTriangle className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
63
+ <p className="text-xs leading-5 text-muted-foreground">
64
+ {t("connection.hybridDeprecated")}
65
+ </p>
66
+ </div>
67
+ ) : null}
68
+
69
+ {connectionsQuery.isError ? (
70
+ <div className="mt-6 flex items-center gap-3 rounded-lg border border-destructive/40 bg-destructive/5 px-4 py-3">
71
+ <IconAlertTriangle className="size-4 shrink-0 text-destructive" />
72
+ <p className="flex-1 text-sm">
73
+ {connectionsQuery.error instanceof Error
74
+ ? connectionsQuery.error.message
75
+ : t("connection.loadFailed")}
76
+ </p>
77
+ <Button
78
+ variant="outline"
79
+ size="sm"
80
+ onClick={() => void connectionsQuery.refetch()}
81
+ >
82
+ {t("connection.retry")}
83
+ </Button>
84
+ </div>
85
+ ) : connections.length ? (
86
+ <div className="mt-6 divide-y divide-border/70 rounded-lg border border-border/70 bg-card">
87
+ {connections.map((connection) => (
88
+ <ConnectionRow key={connection.id} connection={connection} />
89
+ ))}
90
+ </div>
91
+ ) : (
92
+ <div className="mt-6 rounded-lg border border-dashed border-border px-4 py-10 text-center">
93
+ <p className="text-sm font-medium">
94
+ {connectionsQuery.isLoading
95
+ ? t("connection.loading")
96
+ : t("connection.emptyTitle")}
97
+ </p>
98
+ <p className="mt-1 text-sm text-muted-foreground">
99
+ {t("connection.emptyDescription")}
100
+ </p>
101
+ <Button asChild variant="outline" className="mt-4">
102
+ <Link to="/setup">{t("connection.openSetup")}</Link>
103
+ </Button>
104
+ </div>
105
+ )}
106
+
107
+ <div className="mt-4 flex flex-wrap gap-2">
108
+ <Button asChild variant="outline" size="sm">
109
+ <Link to="/setup">{t("connection.openSetup")}</Link>
110
+ </Button>
111
+ <Button asChild variant="outline" size="sm">
112
+ <Link to="/settings/connections">
113
+ {t("connection.openWorkspaceConnections")}
114
+ </Link>
115
+ </Button>
116
+ </div>
117
+ </div>
118
+ );
119
+ }
120
+
121
+ function ConnectionRow({ connection }: { connection: CrmConnectionSummary }) {
122
+ const t = useT();
123
+ const mode = connectionModeInfo(connection.mode);
124
+
125
+ return (
126
+ <section className="flex flex-wrap items-start gap-4 px-4 py-3.5">
127
+ <IconPlugConnected className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
128
+ <div className="min-w-0 flex-1">
129
+ <div className="flex flex-wrap items-center gap-2">
130
+ <p className="truncate text-sm font-medium">{connection.label}</p>
131
+ <Badge variant="outline" className="font-normal">
132
+ {connection.provider}
133
+ </Badge>
134
+ {mode ? (
135
+ <Badge
136
+ variant={mode.deprecated ? "outline" : "secondary"}
137
+ className="font-normal"
138
+ >
139
+ {t(mode.labelKey)}
140
+ {mode.deprecated ? ` · ${t("connection.deprecated")}` : ""}
141
+ </Badge>
142
+ ) : (
143
+ // Never fall back to a default label: an unrecognized mode means
144
+ // this build cannot read the row, which is not the same as native.
145
+ <Badge variant="destructive" className="font-normal">
146
+ {t("connection.modeUnrecognized", { mode: connection.mode })}
147
+ </Badge>
148
+ )}
149
+ </div>
150
+ <p className="mt-1 text-xs leading-5 text-muted-foreground">
151
+ {mode ? t(mode.descriptionKey) : t("connection.modeUnrecognizedHelp")}
152
+ </p>
153
+ <p className="mt-1 text-xs text-muted-foreground">
154
+ {t("connection.mirroredObjects", {
155
+ objects: connection.objectTypes.join(", ") || t("connection.none"),
156
+ })}
157
+ </p>
158
+ {connection.lastError ? (
159
+ <p className="mt-1 text-xs text-destructive">
160
+ {connection.lastError}
161
+ </p>
162
+ ) : null}
163
+ </div>
164
+ <Badge variant="outline" className="shrink-0 font-normal">
165
+ {connection.status}
166
+ </Badge>
167
+ </section>
168
+ );
169
+ }