@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.
- package/CHANGELOG.md +28 -0
- package/dist/approval-events.d.ts +16 -0
- package/dist/approval-events.d.ts.map +1 -0
- package/dist/approval-events.js +27 -0
- package/dist/approval-gate.d.ts +39 -0
- package/dist/approval-gate.d.ts.map +1 -0
- package/dist/approval-gate.js +110 -0
- package/dist/approval-pin-dialog.d.ts +25 -0
- package/dist/approval-pin-dialog.d.ts.map +1 -0
- package/dist/approval-pin-dialog.js +66 -0
- package/dist/business/catalog.d.ts +16 -0
- package/dist/business/catalog.d.ts.map +1 -0
- package/dist/business/catalog.js +42 -0
- package/dist/business/customer-picker.d.ts +39 -0
- package/dist/business/customer-picker.d.ts.map +1 -0
- package/dist/business/customer-picker.js +51 -0
- package/dist/business/feedback.d.ts +72 -0
- package/dist/business/feedback.d.ts.map +1 -0
- package/dist/business/feedback.js +52 -0
- package/dist/business/field-errors.d.ts +26 -0
- package/dist/business/field-errors.d.ts.map +1 -0
- package/dist/business/field-errors.js +59 -0
- package/dist/business/format.d.ts +49 -0
- package/dist/business/format.d.ts.map +1 -0
- package/dist/business/format.js +128 -0
- package/dist/business/index.d.ts +14 -0
- package/dist/business/index.d.ts.map +1 -0
- package/dist/business/index.js +13 -0
- package/dist/business/line-items-editor.d.ts +25 -0
- package/dist/business/line-items-editor.d.ts.map +1 -0
- package/dist/business/line-items-editor.js +36 -0
- package/dist/business/line-items.d.ts +69 -0
- package/dist/business/line-items.d.ts.map +1 -0
- package/dist/business/line-items.js +133 -0
- package/dist/business/payment-capture.d.ts +21 -0
- package/dist/business/payment-capture.d.ts.map +1 -0
- package/dist/business/payment-capture.js +24 -0
- package/dist/business/payment.d.ts +61 -0
- package/dist/business/payment.d.ts.map +1 -0
- package/dist/business/payment.js +74 -0
- package/dist/business/product-picker.d.ts +24 -0
- package/dist/business/product-picker.d.ts.map +1 -0
- package/dist/business/product-picker.js +48 -0
- package/dist/business/product-search.d.ts +70 -0
- package/dist/business/product-search.d.ts.map +1 -0
- package/dist/business/product-search.js +51 -0
- package/dist/business/refund-destination.d.ts +19 -0
- package/dist/business/refund-destination.d.ts.map +1 -0
- package/dist/business/refund-destination.js +31 -0
- package/dist/business/refund.d.ts +29 -0
- package/dist/business/refund.d.ts.map +1 -0
- package/dist/business/refund.js +43 -0
- package/dist/business/use-async-search.d.ts +10 -0
- package/dist/business/use-async-search.d.ts.map +1 -0
- package/dist/business/use-async-search.js +38 -0
- package/dist/document/action-bar.d.ts +12 -0
- package/dist/document/action-bar.d.ts.map +1 -0
- package/dist/document/action-bar.js +39 -0
- package/dist/document/cancel-with-reason.d.ts +47 -0
- package/dist/document/cancel-with-reason.d.ts.map +1 -0
- package/dist/document/cancel-with-reason.js +52 -0
- package/dist/document/document-header.d.ts +41 -0
- package/dist/document/document-header.d.ts.map +1 -0
- package/dist/document/document-header.js +30 -0
- package/dist/document/document-page.d.ts +37 -0
- package/dist/document/document-page.d.ts.map +1 -0
- package/dist/document/document-page.js +137 -0
- package/dist/document/document-timeline.d.ts +20 -0
- package/dist/document/document-timeline.d.ts.map +1 -0
- package/dist/document/document-timeline.js +26 -0
- package/dist/document/evaluate.d.ts +31 -0
- package/dist/document/evaluate.d.ts.map +1 -0
- package/dist/document/evaluate.js +170 -0
- package/dist/document/format.d.ts +9 -0
- package/dist/document/format.d.ts.map +1 -0
- package/dist/document/format.js +52 -0
- package/dist/document/index.d.ts +13 -0
- package/dist/document/index.d.ts.map +1 -0
- package/dist/document/index.js +12 -0
- package/dist/document/registry.d.ts +20 -0
- package/dist/document/registry.d.ts.map +1 -0
- package/dist/document/registry.js +20 -0
- package/dist/document/smart-buttons.d.ts +16 -0
- package/dist/document/smart-buttons.d.ts.map +1 -0
- package/dist/document/smart-buttons.js +16 -0
- package/dist/document/status-badge.d.ts +16 -0
- package/dist/document/status-badge.d.ts.map +1 -0
- package/dist/document/status-badge.js +23 -0
- package/dist/document/types.d.ts +196 -0
- package/dist/document/types.d.ts.map +1 -0
- package/dist/document/types.js +1 -0
- package/dist/document/use-document-sources.d.ts +14 -0
- package/dist/document/use-document-sources.d.ts.map +1 -0
- package/dist/document/use-document-sources.js +72 -0
- package/dist/dynamic-columns.d.ts +11 -0
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +13 -1
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +4 -5
- package/dist/dynamic-row-actions.d.ts.map +1 -1
- package/dist/dynamic-row-actions.js +12 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/model-action-toolbar.d.ts.map +1 -1
- package/dist/model-action-toolbar.js +16 -3
- package/dist/requires-addon.d.ts +37 -0
- package/dist/requires-addon.d.ts.map +1 -0
- package/dist/requires-addon.js +104 -0
- package/dist/server-error.d.ts.map +1 -1
- package/dist/server-error.js +19 -0
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/approval-gate.test.tsx +134 -0
- package/src/__tests__/requires-addon.test.tsx +201 -0
- package/src/approval-events.ts +39 -0
- package/src/approval-gate.tsx +180 -0
- package/src/approval-pin-dialog.tsx +162 -0
- package/src/business/business.test.ts +164 -0
- package/src/business/catalog.ts +60 -0
- package/src/business/customer-picker.tsx +201 -0
- package/src/business/feedback.tsx +141 -0
- package/src/business/field-errors.ts +92 -0
- package/src/business/format.ts +151 -0
- package/src/business/index.ts +13 -0
- package/src/business/line-items-editor.tsx +259 -0
- package/src/business/line-items.ts +190 -0
- package/src/business/payment-capture.tsx +158 -0
- package/src/business/payment.ts +125 -0
- package/src/business/product-picker.tsx +171 -0
- package/src/business/product-search.ts +108 -0
- package/src/business/refund-destination.tsx +121 -0
- package/src/business/refund.ts +71 -0
- package/src/business/use-async-search.ts +43 -0
- package/src/document/__tests__/document-page.test.tsx +107 -0
- package/src/document/__tests__/evaluate.test.ts +140 -0
- package/src/document/action-bar.tsx +140 -0
- package/src/document/cancel-with-reason.tsx +214 -0
- package/src/document/document-header.tsx +140 -0
- package/src/document/document-page.tsx +268 -0
- package/src/document/document-timeline.tsx +66 -0
- package/src/document/evaluate.ts +180 -0
- package/src/document/format.ts +59 -0
- package/src/document/index.ts +28 -0
- package/src/document/registry.ts +39 -0
- package/src/document/smart-buttons.tsx +54 -0
- package/src/document/status-badge.tsx +44 -0
- package/src/document/types.ts +203 -0
- package/src/document/use-document-sources.ts +91 -0
- package/src/dynamic-columns.tsx +36 -6
- package/src/dynamic-kanban.tsx +6 -7
- package/src/dynamic-row-actions.tsx +15 -1
- package/src/index.ts +34 -0
- package/src/model-action-toolbar.tsx +28 -3
- package/src/requires-addon.tsx +160 -0
- package/src/server-error.ts +20 -0
- 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
|
+
}
|
package/src/server-error.ts
CHANGED
|
@@ -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
|
}
|