@asteby/metacore-runtime-react 44.0.0 → 45.2.0

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 (158) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/approval-events.d.ts +16 -0
  3. package/dist/approval-events.d.ts.map +1 -0
  4. package/dist/approval-events.js +27 -0
  5. package/dist/approval-gate.d.ts +39 -0
  6. package/dist/approval-gate.d.ts.map +1 -0
  7. package/dist/approval-gate.js +110 -0
  8. package/dist/approval-pin-dialog.d.ts +25 -0
  9. package/dist/approval-pin-dialog.d.ts.map +1 -0
  10. package/dist/approval-pin-dialog.js +66 -0
  11. package/dist/business/catalog.d.ts +16 -0
  12. package/dist/business/catalog.d.ts.map +1 -0
  13. package/dist/business/catalog.js +42 -0
  14. package/dist/business/customer-picker.d.ts +39 -0
  15. package/dist/business/customer-picker.d.ts.map +1 -0
  16. package/dist/business/customer-picker.js +51 -0
  17. package/dist/business/feedback.d.ts +72 -0
  18. package/dist/business/feedback.d.ts.map +1 -0
  19. package/dist/business/feedback.js +52 -0
  20. package/dist/business/field-errors.d.ts +26 -0
  21. package/dist/business/field-errors.d.ts.map +1 -0
  22. package/dist/business/field-errors.js +59 -0
  23. package/dist/business/format.d.ts +49 -0
  24. package/dist/business/format.d.ts.map +1 -0
  25. package/dist/business/format.js +128 -0
  26. package/dist/business/index.d.ts +14 -0
  27. package/dist/business/index.d.ts.map +1 -0
  28. package/dist/business/index.js +13 -0
  29. package/dist/business/line-items-editor.d.ts +25 -0
  30. package/dist/business/line-items-editor.d.ts.map +1 -0
  31. package/dist/business/line-items-editor.js +36 -0
  32. package/dist/business/line-items.d.ts +69 -0
  33. package/dist/business/line-items.d.ts.map +1 -0
  34. package/dist/business/line-items.js +133 -0
  35. package/dist/business/payment-capture.d.ts +21 -0
  36. package/dist/business/payment-capture.d.ts.map +1 -0
  37. package/dist/business/payment-capture.js +24 -0
  38. package/dist/business/payment.d.ts +61 -0
  39. package/dist/business/payment.d.ts.map +1 -0
  40. package/dist/business/payment.js +74 -0
  41. package/dist/business/product-picker.d.ts +24 -0
  42. package/dist/business/product-picker.d.ts.map +1 -0
  43. package/dist/business/product-picker.js +48 -0
  44. package/dist/business/product-search.d.ts +70 -0
  45. package/dist/business/product-search.d.ts.map +1 -0
  46. package/dist/business/product-search.js +51 -0
  47. package/dist/business/refund-destination.d.ts +19 -0
  48. package/dist/business/refund-destination.d.ts.map +1 -0
  49. package/dist/business/refund-destination.js +31 -0
  50. package/dist/business/refund.d.ts +29 -0
  51. package/dist/business/refund.d.ts.map +1 -0
  52. package/dist/business/refund.js +43 -0
  53. package/dist/business/use-async-search.d.ts +10 -0
  54. package/dist/business/use-async-search.d.ts.map +1 -0
  55. package/dist/business/use-async-search.js +38 -0
  56. package/dist/document/action-bar.d.ts +12 -0
  57. package/dist/document/action-bar.d.ts.map +1 -0
  58. package/dist/document/action-bar.js +39 -0
  59. package/dist/document/cancel-with-reason.d.ts +47 -0
  60. package/dist/document/cancel-with-reason.d.ts.map +1 -0
  61. package/dist/document/cancel-with-reason.js +52 -0
  62. package/dist/document/document-header.d.ts +41 -0
  63. package/dist/document/document-header.d.ts.map +1 -0
  64. package/dist/document/document-header.js +30 -0
  65. package/dist/document/document-page.d.ts +37 -0
  66. package/dist/document/document-page.d.ts.map +1 -0
  67. package/dist/document/document-page.js +137 -0
  68. package/dist/document/document-timeline.d.ts +20 -0
  69. package/dist/document/document-timeline.d.ts.map +1 -0
  70. package/dist/document/document-timeline.js +26 -0
  71. package/dist/document/evaluate.d.ts +31 -0
  72. package/dist/document/evaluate.d.ts.map +1 -0
  73. package/dist/document/evaluate.js +170 -0
  74. package/dist/document/format.d.ts +9 -0
  75. package/dist/document/format.d.ts.map +1 -0
  76. package/dist/document/format.js +52 -0
  77. package/dist/document/index.d.ts +13 -0
  78. package/dist/document/index.d.ts.map +1 -0
  79. package/dist/document/index.js +12 -0
  80. package/dist/document/registry.d.ts +20 -0
  81. package/dist/document/registry.d.ts.map +1 -0
  82. package/dist/document/registry.js +20 -0
  83. package/dist/document/smart-buttons.d.ts +16 -0
  84. package/dist/document/smart-buttons.d.ts.map +1 -0
  85. package/dist/document/smart-buttons.js +16 -0
  86. package/dist/document/status-badge.d.ts +16 -0
  87. package/dist/document/status-badge.d.ts.map +1 -0
  88. package/dist/document/status-badge.js +23 -0
  89. package/dist/document/types.d.ts +196 -0
  90. package/dist/document/types.d.ts.map +1 -0
  91. package/dist/document/types.js +1 -0
  92. package/dist/document/use-document-sources.d.ts +14 -0
  93. package/dist/document/use-document-sources.d.ts.map +1 -0
  94. package/dist/document/use-document-sources.js +72 -0
  95. package/dist/dynamic-columns.d.ts +11 -0
  96. package/dist/dynamic-columns.d.ts.map +1 -1
  97. package/dist/dynamic-columns.js +13 -1
  98. package/dist/dynamic-kanban.d.ts.map +1 -1
  99. package/dist/dynamic-kanban.js +4 -5
  100. package/dist/dynamic-row-actions.d.ts.map +1 -1
  101. package/dist/dynamic-row-actions.js +12 -1
  102. package/dist/index.d.ts +5 -0
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +6 -0
  105. package/dist/model-action-toolbar.d.ts.map +1 -1
  106. package/dist/model-action-toolbar.js +16 -3
  107. package/dist/requires-addon.d.ts +37 -0
  108. package/dist/requires-addon.d.ts.map +1 -0
  109. package/dist/requires-addon.js +104 -0
  110. package/dist/server-error.d.ts.map +1 -1
  111. package/dist/server-error.js +19 -0
  112. package/dist/types.d.ts +23 -0
  113. package/dist/types.d.ts.map +1 -1
  114. package/package.json +3 -3
  115. package/src/__tests__/approval-gate.test.tsx +134 -0
  116. package/src/__tests__/requires-addon.test.tsx +201 -0
  117. package/src/approval-events.ts +39 -0
  118. package/src/approval-gate.tsx +180 -0
  119. package/src/approval-pin-dialog.tsx +162 -0
  120. package/src/business/business.test.ts +164 -0
  121. package/src/business/catalog.ts +60 -0
  122. package/src/business/customer-picker.tsx +201 -0
  123. package/src/business/feedback.tsx +141 -0
  124. package/src/business/field-errors.ts +92 -0
  125. package/src/business/format.ts +151 -0
  126. package/src/business/index.ts +13 -0
  127. package/src/business/line-items-editor.tsx +259 -0
  128. package/src/business/line-items.ts +190 -0
  129. package/src/business/payment-capture.tsx +158 -0
  130. package/src/business/payment.ts +125 -0
  131. package/src/business/product-picker.tsx +171 -0
  132. package/src/business/product-search.ts +108 -0
  133. package/src/business/refund-destination.tsx +121 -0
  134. package/src/business/refund.ts +71 -0
  135. package/src/business/use-async-search.ts +43 -0
  136. package/src/document/__tests__/document-page.test.tsx +107 -0
  137. package/src/document/__tests__/evaluate.test.ts +140 -0
  138. package/src/document/action-bar.tsx +140 -0
  139. package/src/document/cancel-with-reason.tsx +214 -0
  140. package/src/document/document-header.tsx +140 -0
  141. package/src/document/document-page.tsx +268 -0
  142. package/src/document/document-timeline.tsx +66 -0
  143. package/src/document/evaluate.ts +180 -0
  144. package/src/document/format.ts +59 -0
  145. package/src/document/index.ts +28 -0
  146. package/src/document/registry.ts +39 -0
  147. package/src/document/smart-buttons.tsx +54 -0
  148. package/src/document/status-badge.tsx +44 -0
  149. package/src/document/types.ts +203 -0
  150. package/src/document/use-document-sources.ts +91 -0
  151. package/src/dynamic-columns.tsx +36 -6
  152. package/src/dynamic-kanban.tsx +6 -7
  153. package/src/dynamic-row-actions.tsx +15 -1
  154. package/src/index.ts +34 -0
  155. package/src/model-action-toolbar.tsx +28 -3
  156. package/src/requires-addon.tsx +160 -0
  157. package/src/server-error.ts +20 -0
  158. package/src/types.ts +24 -0
@@ -0,0 +1,160 @@
1
+ // requiresAddon — an action that depends on an OPTIONAL addon the org has not
2
+ // installed yet (e.g. link_inbox "Conectar WhatsApp" needs connector_whatsapp).
3
+ //
4
+ // The manifest declares `compatibility.requires[].enables: ["action:<Model>.<key>"]`
5
+ // and the host stamps each affected action's metadata, only while the required
6
+ // addon is missing:
7
+ //
8
+ // "requires_addon": { "key": "connector_whatsapp", "name": "Conector WhatsApp", "reason": "…" }
9
+ //
10
+ // The action is NOT hidden (the user should discover the feature) and NOT
11
+ // executed: row menus / kanban cards / the model toolbar render it with a lock
12
+ // and a tooltip, and a click opens <RequiresAddonDialog> instead of the action
13
+ // dispatcher. "Instalar" delegates to the host (setAddonInstallHandler) or, when
14
+ // none is registered, navigates to `/marketplace/<key>` through the SDK router so
15
+ // the host basepath (ops lives under /app) is respected.
16
+ import { useNavigate } from '@tanstack/react-router'
17
+ import { useTranslation } from 'react-i18next'
18
+ import { Lock } from 'lucide-react'
19
+ import {
20
+ AlertDialog,
21
+ AlertDialogAction,
22
+ AlertDialogCancel,
23
+ AlertDialogContent,
24
+ AlertDialogDescription,
25
+ AlertDialogFooter,
26
+ AlertDialogHeader,
27
+ AlertDialogTitle,
28
+ } from '@asteby/metacore-ui/primitives'
29
+ import type { RequiresAddon } from './types'
30
+
31
+ export type { RequiresAddon }
32
+
33
+ /**
34
+ * Reads the addon requirement off an action, tolerating both the camelCase
35
+ * `requiresAddon` and the snake_case `requires_addon` the host serves. Returns
36
+ * null when the action is freely executable (no requirement, or a malformed one
37
+ * without a `key` — degrade to "runs", never to "locked forever").
38
+ */
39
+ export function resolveRequiresAddon(action: any): RequiresAddon | null {
40
+ const raw = action?.requiresAddon ?? action?.requires_addon
41
+ if (!raw || typeof raw !== 'object') return null
42
+ const key = typeof raw.key === 'string' ? raw.key.trim() : ''
43
+ if (!key) return null
44
+ const out: RequiresAddon = { key }
45
+ if (typeof raw.name === 'string' && raw.name) out.name = raw.name
46
+ if (typeof raw.reason === 'string' && raw.reason) out.reason = raw.reason
47
+ return out
48
+ }
49
+
50
+ type AddonInstallHandler = (key: string) => void
51
+
52
+ let installHandler: AddonInstallHandler | null = null
53
+
54
+ /**
55
+ * Registers the host's "install this addon" flow (e.g. open the marketplace
56
+ * install drawer). Pass `null` to unregister. Without a handler the SDK
57
+ * navigates to `/marketplace/<key>` via the router.
58
+ */
59
+ export function setAddonInstallHandler(fn: AddonInstallHandler | null): void {
60
+ installHandler = fn
61
+ }
62
+
63
+ /** Current host install handler, or null when none is registered. */
64
+ export function getAddonInstallHandler(): AddonInstallHandler | null {
65
+ return installHandler
66
+ }
67
+
68
+ /** Display name for a requirement: the host-provided name, else the key. */
69
+ export function requiresAddonName(req: RequiresAddon): string {
70
+ return req.name || req.key
71
+ }
72
+
73
+ /** Tooltip text for a gated action: "Requiere «<name>»". */
74
+ export function useRequiresAddonLabel(): (req: RequiresAddon) => string {
75
+ const { t } = useTranslation()
76
+ return (req) =>
77
+ t('dynamic.requires_addon_badge', {
78
+ defaultValue: 'Requiere «{{name}}»',
79
+ name: requiresAddonName(req),
80
+ })
81
+ }
82
+
83
+ /** Lock marker rendered next to a gated action's label (hover = addon name). */
84
+ export function RequiresAddonLock({ requirement }: { requirement: RequiresAddon }) {
85
+ const label = useRequiresAddonLabel()(requirement)
86
+ return (
87
+ <span
88
+ className="ml-auto inline-flex shrink-0 items-center pl-2 text-muted-foreground"
89
+ title={label}
90
+ aria-label={label}
91
+ data-requires-addon={requirement.key}
92
+ >
93
+ <Lock className="h-3.5 w-3.5" />
94
+ </span>
95
+ )
96
+ }
97
+
98
+ export interface RequiresAddonDialogProps {
99
+ /** The requirement to explain; the dialog renders nothing while null. */
100
+ requirement: RequiresAddon | null
101
+ onOpenChange: (open: boolean) => void
102
+ }
103
+
104
+ /**
105
+ * Compact "this action requires <addon>" dialog. Never touches the backend:
106
+ * "Instalar" hands the key to the host handler or routes to the marketplace.
107
+ */
108
+ export function RequiresAddonDialog({ requirement, onOpenChange }: RequiresAddonDialogProps) {
109
+ const { t } = useTranslation()
110
+ const navigate = useNavigate()
111
+ if (!requirement) return null
112
+ const name = requiresAddonName(requirement)
113
+
114
+ const install = () => {
115
+ onOpenChange(false)
116
+ const handler = installHandler
117
+ if (handler) handler(requirement.key)
118
+ else navigate({ to: `/marketplace/${encodeURIComponent(requirement.key)}` })
119
+ }
120
+
121
+ return (
122
+ <AlertDialog open onOpenChange={onOpenChange}>
123
+ <AlertDialogContent className="sm:max-w-md">
124
+ <AlertDialogHeader>
125
+ <AlertDialogTitle className="flex items-center gap-2">
126
+ <Lock className="h-4 w-4 shrink-0 text-muted-foreground" />
127
+ {t('dynamic.requires_addon_title', {
128
+ defaultValue: 'Esta acción requiere «{{name}}»',
129
+ name,
130
+ })}
131
+ </AlertDialogTitle>
132
+ <AlertDialogDescription>
133
+ {requirement.reason
134
+ ? t(requirement.reason, { defaultValue: requirement.reason })
135
+ : t('dynamic.requires_addon_description', {
136
+ defaultValue: 'Instala «{{name}}» para habilitar esta acción.',
137
+ name,
138
+ })}
139
+ </AlertDialogDescription>
140
+ </AlertDialogHeader>
141
+ <AlertDialogFooter>
142
+ <AlertDialogCancel>
143
+ {t('dynamic.requires_addon_cancel', { defaultValue: 'Cancelar' })}
144
+ </AlertDialogCancel>
145
+ <AlertDialogAction
146
+ onClick={(e: React.MouseEvent) => {
147
+ e.preventDefault()
148
+ install()
149
+ }}
150
+ >
151
+ {t('dynamic.requires_addon_install', {
152
+ defaultValue: 'Instalar «{{name}}»',
153
+ name,
154
+ })}
155
+ </AlertDialogAction>
156
+ </AlertDialogFooter>
157
+ </AlertDialogContent>
158
+ </AlertDialog>
159
+ )
160
+ }
@@ -11,6 +11,7 @@
11
11
  // the toast description, in ONE place so every call site behaves identically.
12
12
  import { toast } from 'sonner'
13
13
  import { validationCatalog, validationMessageKey } from './validation-catalog'
14
+ import { APPROVAL_PARKED_EVENT, approvalGateAvailable, approvalRequiredInfo } from './approval-events'
14
15
 
15
16
  /** Structured, display-ready view of an error: a headline + an optional cause. */
16
17
  export interface ExtractedError {
@@ -234,6 +235,25 @@ export function toastServerError(
234
235
  const t = opts?.t
235
236
  const lang = opts?.language
236
237
  const cat = validationCatalog(lang)
238
+ // A write parked for approval is not a failure: say so, and when an
239
+ // <ApprovalGateProvider> is mounted offer to approve it right now with a PIN.
240
+ const parked = approvalRequiredInfo(err)
241
+ if (parked) {
242
+ const title = parked.label || (t ? t('approvals.sent', { defaultValue: 'Enviado a aprobación' }) : 'Enviado a aprobación')
243
+ toast.info(title, {
244
+ description: parked.roles.length
245
+ ? (t ? t('approvals.waiting_for', { defaultValue: 'Lo decide: {{roles}}', roles: parked.roles.join(', ') }) : parked.roles.join(', '))
246
+ : undefined,
247
+ action:
248
+ parked.requestId && approvalGateAvailable()
249
+ ? {
250
+ label: t ? t('approvals.approve_with_pin', { defaultValue: 'Aprobar con PIN' }) : 'Aprobar con PIN',
251
+ onClick: () => window.dispatchEvent(new CustomEvent(APPROVAL_PARKED_EVENT, { detail: parked })),
252
+ }
253
+ : undefined,
254
+ })
255
+ return
256
+ }
237
257
  const map = extractFieldErrors(err)
238
258
  if (map) {
239
259
  const localized = t
package/src/types.ts CHANGED
@@ -654,6 +654,16 @@ export interface FieldOptionsConfig {
654
654
  image?: string
655
655
  }
656
656
 
657
+ /** An optional addon an action needs before it can run. */
658
+ export interface RequiresAddon {
659
+ /** Addon key, e.g. "connector_whatsapp". */
660
+ key: string
661
+ /** Localized display name, e.g. "Conector WhatsApp". Falls back to `key`. */
662
+ name?: string
663
+ /** Why the action needs it (literal or i18n key). */
664
+ reason?: string
665
+ }
666
+
657
667
  export interface ActionDefinition {
658
668
  key: string
659
669
  name: string
@@ -680,6 +690,13 @@ export interface ActionDefinition {
680
690
  * "create" — toolbar button that replaces the generic create button.
681
691
  */
682
692
  placement?: 'row' | 'table' | 'create'
693
+ /**
694
+ * Optional addon this action depends on, stamped by the host only while it
695
+ * is NOT installed (served as `requires_addon`; read through
696
+ * resolveRequiresAddon). The action stays visible but locked: a click opens
697
+ * the "requires addon" dialog instead of executing.
698
+ */
699
+ requiresAddon?: RequiresAddon
683
700
  }
684
701
 
685
702
  export interface ApiResponse<T> {
@@ -724,4 +741,11 @@ export interface ActionMetadata {
724
741
  /** Federated modal slug "<addon_key>.<action_key>"; see ActionModalDispatcher. */
725
742
  modal?: string
726
743
  placement?: 'row' | 'table' | 'create'
744
+ /**
745
+ * Optional addon this action depends on, stamped by the host only while it
746
+ * is NOT installed (served as `requires_addon`; read through
747
+ * resolveRequiresAddon). The action stays visible but locked: a click opens
748
+ * the "requires addon" dialog instead of executing.
749
+ */
750
+ requiresAddon?: RequiresAddon
727
751
  }