@ethisyscore/plugin-ui 1.90.0 → 1.91.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.
@@ -330,6 +330,49 @@ declare const ORGANISATION_SETTINGS_URI = "ethisys:organisation-settings";
330
330
  */
331
331
  declare function useOrgFormatSync(): void;
332
332
 
333
+ /** Host tool that resolves the caller org's reporting currency (default, else GBP). */
334
+ declare const REPORTING_CURRENCY_TOOL = "settings:get-organisation-reporting-currency";
335
+ /** Host tool that lists the currency catalogue. */
336
+ declare const LIST_CURRENCIES_TOOL = "settings:list-currencies";
337
+ /** A currency as returned by the host currency tools. */
338
+ interface CurrencyInfo {
339
+ readonly id: string;
340
+ readonly name: string;
341
+ readonly code: string;
342
+ readonly symbol: string | null;
343
+ /** Host decimal-places override, or null to use the currency's ISO default. */
344
+ readonly decimalPlaces: number | null;
345
+ }
346
+ /** What a plugin surface may pass to {@link UseCurrencyResult.format} to name a currency. */
347
+ type CurrencySelector = CurrencyInfo | string | null | undefined;
348
+ interface UseCurrencyResult {
349
+ /** The org's reporting currency, or null while loading / when unresolved. */
350
+ reportingCurrency: CurrencyInfo | null;
351
+ /** The currency catalogue keyed by id (empty while loading / on a fail-open). */
352
+ currencies: ReadonlyMap<string, CurrencyInfo>;
353
+ /** True once the initial fetch has settled (whether it succeeded or failed open). */
354
+ ready: boolean;
355
+ /**
356
+ * Formats an amount in a currency. `currency` may be a {@link CurrencyInfo}, a
357
+ * currency id or ISO code (resolved via the catalogue), or null/undefined to use
358
+ * the org's reporting currency. When no currency can be resolved it falls back to a
359
+ * plain localized number so a render never throws or shows a wrong symbol.
360
+ */
361
+ format: (amount: number, currency?: CurrencySelector) => string;
362
+ }
363
+ /**
364
+ * Host-aware currency access for a plugin surface: the org's reporting currency, the
365
+ * currency catalogue, and a `format` helper that renders through the pure
366
+ * `@ethisyscore/core-utils/money` formatter honouring each currency's decimal-places
367
+ * override.
368
+ *
369
+ * Fails open, like {@link useOrgFormatSync}: it resolves the transport via the OPTIONAL
370
+ * accessor, so a surface rendered without a host transport (standalone/mock run, unit
371
+ * test) degrades to an empty catalogue and a plain-number fallback rather than crashing.
372
+ * Re-fetches when the active organisation changes.
373
+ */
374
+ declare function useCurrency(): UseCurrencyResult;
375
+
333
376
  /**
334
377
  * Class on the portal container element rendered at the end of a plugin surface.
335
378
  * `plugin-base.css` positions it; the class is exported so a plugin can target it
@@ -1348,4 +1391,4 @@ interface MenuConfigHostSidebarOptions {
1348
1391
  */
1349
1392
  declare function menuConfigToHostSidebarInput(config: HeaderMenuItem[], opts: MenuConfigHostSidebarOptions): HostSidebarHookInput;
1350
1393
 
1351
- export { type AuthenticatedQueryResult, BaseMcpService, DEFAULT_MAX_UPLOAD_BYTES, type DefinePlatformReactPluginOverlayOptions, type DefinePlatformReactPluginPageOptions, EHX_PLUGIN_PORTAL_ATTR, EHX_PLUGIN_PORTAL_CLASS, EHX_PLUGIN_ROOT_CLASS, HostSidebarHookInput, HostSidebarInput, type InsetLease, type ManagedLifecycle, type McpToolArgs, type McpToolContract, type McpToolContracts, type McpToolResult, type MenuConfigHostSidebarOptions, MissingViewFallback, ORGANISATION_SETTINGS_URI, OVERLAY_HOST_CONTRACT_VERSION, type OverlayHostApi, type OverlayHostCapabilities, OverlayHostContext, type PlatformReactOverlayProps, type PluginOverlayDefinerConfig, type PluginPageDefinerConfig, PluginPortalScope, PluginStyleScope, type ReactRouterShimOptions, SurfaceBaseContext, type SurfaceBaseValue, TemplateContext, type TemplateManifest, type ToHostSidebarInputOptions, type ToolInvoker, TypedMcpService, type UseMcpUploadOptions, type UseMcpUploadResult, type UseMcpUploadTarget, type ViewComponent, buildSurfaceUrl, createPluginOverlayDefiner, createPluginPageDefiner, createReactRouterShim, createUseView, definePlatformReactPluginOverlay, definePlatformReactPluginPage, deriveHostEntityToken, injectPluginStyles, isComponent, mcpTool, menuConfigToHostSidebarInput, normaliseSlug, notViaMcp, resolveSurfaceBase, surfacePathFor, toHostSidebarInput, useAuthenticatedQueries, useAuthenticatedQuery, useManagedLifecycle, useMcpUpload, useOrgFormatSync, useOverlayHost, usePluginRealtime, useSurfaceUrl, useToolInvoker, useToolInvokerMap, useTypedToolInvokerMap, useView };
1394
+ export { type AuthenticatedQueryResult, BaseMcpService, type CurrencyInfo, type CurrencySelector, DEFAULT_MAX_UPLOAD_BYTES, type DefinePlatformReactPluginOverlayOptions, type DefinePlatformReactPluginPageOptions, EHX_PLUGIN_PORTAL_ATTR, EHX_PLUGIN_PORTAL_CLASS, EHX_PLUGIN_ROOT_CLASS, HostSidebarHookInput, HostSidebarInput, type InsetLease, LIST_CURRENCIES_TOOL, type ManagedLifecycle, type McpToolArgs, type McpToolContract, type McpToolContracts, type McpToolResult, type MenuConfigHostSidebarOptions, MissingViewFallback, ORGANISATION_SETTINGS_URI, OVERLAY_HOST_CONTRACT_VERSION, type OverlayHostApi, type OverlayHostCapabilities, OverlayHostContext, type PlatformReactOverlayProps, type PluginOverlayDefinerConfig, type PluginPageDefinerConfig, PluginPortalScope, PluginStyleScope, REPORTING_CURRENCY_TOOL, type ReactRouterShimOptions, SurfaceBaseContext, type SurfaceBaseValue, TemplateContext, type TemplateManifest, type ToHostSidebarInputOptions, type ToolInvoker, TypedMcpService, type UseCurrencyResult, type UseMcpUploadOptions, type UseMcpUploadResult, type UseMcpUploadTarget, type ViewComponent, buildSurfaceUrl, createPluginOverlayDefiner, createPluginPageDefiner, createReactRouterShim, createUseView, definePlatformReactPluginOverlay, definePlatformReactPluginPage, deriveHostEntityToken, injectPluginStyles, isComponent, mcpTool, menuConfigToHostSidebarInput, normaliseSlug, notViaMcp, resolveSurfaceBase, surfacePathFor, toHostSidebarInput, useAuthenticatedQueries, useAuthenticatedQuery, useCurrency, useManagedLifecycle, useMcpUpload, useOrgFormatSync, useOverlayHost, usePluginRealtime, useSurfaceUrl, useToolInvoker, useToolInvokerMap, useTypedToolInvokerMap, useView };
@@ -330,6 +330,49 @@ declare const ORGANISATION_SETTINGS_URI = "ethisys:organisation-settings";
330
330
  */
331
331
  declare function useOrgFormatSync(): void;
332
332
 
333
+ /** Host tool that resolves the caller org's reporting currency (default, else GBP). */
334
+ declare const REPORTING_CURRENCY_TOOL = "settings:get-organisation-reporting-currency";
335
+ /** Host tool that lists the currency catalogue. */
336
+ declare const LIST_CURRENCIES_TOOL = "settings:list-currencies";
337
+ /** A currency as returned by the host currency tools. */
338
+ interface CurrencyInfo {
339
+ readonly id: string;
340
+ readonly name: string;
341
+ readonly code: string;
342
+ readonly symbol: string | null;
343
+ /** Host decimal-places override, or null to use the currency's ISO default. */
344
+ readonly decimalPlaces: number | null;
345
+ }
346
+ /** What a plugin surface may pass to {@link UseCurrencyResult.format} to name a currency. */
347
+ type CurrencySelector = CurrencyInfo | string | null | undefined;
348
+ interface UseCurrencyResult {
349
+ /** The org's reporting currency, or null while loading / when unresolved. */
350
+ reportingCurrency: CurrencyInfo | null;
351
+ /** The currency catalogue keyed by id (empty while loading / on a fail-open). */
352
+ currencies: ReadonlyMap<string, CurrencyInfo>;
353
+ /** True once the initial fetch has settled (whether it succeeded or failed open). */
354
+ ready: boolean;
355
+ /**
356
+ * Formats an amount in a currency. `currency` may be a {@link CurrencyInfo}, a
357
+ * currency id or ISO code (resolved via the catalogue), or null/undefined to use
358
+ * the org's reporting currency. When no currency can be resolved it falls back to a
359
+ * plain localized number so a render never throws or shows a wrong symbol.
360
+ */
361
+ format: (amount: number, currency?: CurrencySelector) => string;
362
+ }
363
+ /**
364
+ * Host-aware currency access for a plugin surface: the org's reporting currency, the
365
+ * currency catalogue, and a `format` helper that renders through the pure
366
+ * `@ethisyscore/core-utils/money` formatter honouring each currency's decimal-places
367
+ * override.
368
+ *
369
+ * Fails open, like {@link useOrgFormatSync}: it resolves the transport via the OPTIONAL
370
+ * accessor, so a surface rendered without a host transport (standalone/mock run, unit
371
+ * test) degrades to an empty catalogue and a plain-number fallback rather than crashing.
372
+ * Re-fetches when the active organisation changes.
373
+ */
374
+ declare function useCurrency(): UseCurrencyResult;
375
+
333
376
  /**
334
377
  * Class on the portal container element rendered at the end of a plugin surface.
335
378
  * `plugin-base.css` positions it; the class is exported so a plugin can target it
@@ -1348,4 +1391,4 @@ interface MenuConfigHostSidebarOptions {
1348
1391
  */
1349
1392
  declare function menuConfigToHostSidebarInput(config: HeaderMenuItem[], opts: MenuConfigHostSidebarOptions): HostSidebarHookInput;
1350
1393
 
1351
- export { type AuthenticatedQueryResult, BaseMcpService, DEFAULT_MAX_UPLOAD_BYTES, type DefinePlatformReactPluginOverlayOptions, type DefinePlatformReactPluginPageOptions, EHX_PLUGIN_PORTAL_ATTR, EHX_PLUGIN_PORTAL_CLASS, EHX_PLUGIN_ROOT_CLASS, HostSidebarHookInput, HostSidebarInput, type InsetLease, type ManagedLifecycle, type McpToolArgs, type McpToolContract, type McpToolContracts, type McpToolResult, type MenuConfigHostSidebarOptions, MissingViewFallback, ORGANISATION_SETTINGS_URI, OVERLAY_HOST_CONTRACT_VERSION, type OverlayHostApi, type OverlayHostCapabilities, OverlayHostContext, type PlatformReactOverlayProps, type PluginOverlayDefinerConfig, type PluginPageDefinerConfig, PluginPortalScope, PluginStyleScope, type ReactRouterShimOptions, SurfaceBaseContext, type SurfaceBaseValue, TemplateContext, type TemplateManifest, type ToHostSidebarInputOptions, type ToolInvoker, TypedMcpService, type UseMcpUploadOptions, type UseMcpUploadResult, type UseMcpUploadTarget, type ViewComponent, buildSurfaceUrl, createPluginOverlayDefiner, createPluginPageDefiner, createReactRouterShim, createUseView, definePlatformReactPluginOverlay, definePlatformReactPluginPage, deriveHostEntityToken, injectPluginStyles, isComponent, mcpTool, menuConfigToHostSidebarInput, normaliseSlug, notViaMcp, resolveSurfaceBase, surfacePathFor, toHostSidebarInput, useAuthenticatedQueries, useAuthenticatedQuery, useManagedLifecycle, useMcpUpload, useOrgFormatSync, useOverlayHost, usePluginRealtime, useSurfaceUrl, useToolInvoker, useToolInvokerMap, useTypedToolInvokerMap, useView };
1394
+ export { type AuthenticatedQueryResult, BaseMcpService, type CurrencyInfo, type CurrencySelector, DEFAULT_MAX_UPLOAD_BYTES, type DefinePlatformReactPluginOverlayOptions, type DefinePlatformReactPluginPageOptions, EHX_PLUGIN_PORTAL_ATTR, EHX_PLUGIN_PORTAL_CLASS, EHX_PLUGIN_ROOT_CLASS, HostSidebarHookInput, HostSidebarInput, type InsetLease, LIST_CURRENCIES_TOOL, type ManagedLifecycle, type McpToolArgs, type McpToolContract, type McpToolContracts, type McpToolResult, type MenuConfigHostSidebarOptions, MissingViewFallback, ORGANISATION_SETTINGS_URI, OVERLAY_HOST_CONTRACT_VERSION, type OverlayHostApi, type OverlayHostCapabilities, OverlayHostContext, type PlatformReactOverlayProps, type PluginOverlayDefinerConfig, type PluginPageDefinerConfig, PluginPortalScope, PluginStyleScope, REPORTING_CURRENCY_TOOL, type ReactRouterShimOptions, SurfaceBaseContext, type SurfaceBaseValue, TemplateContext, type TemplateManifest, type ToHostSidebarInputOptions, type ToolInvoker, TypedMcpService, type UseCurrencyResult, type UseMcpUploadOptions, type UseMcpUploadResult, type UseMcpUploadTarget, type ViewComponent, buildSurfaceUrl, createPluginOverlayDefiner, createPluginPageDefiner, createReactRouterShim, createUseView, definePlatformReactPluginOverlay, definePlatformReactPluginPage, deriveHostEntityToken, injectPluginStyles, isComponent, mcpTool, menuConfigToHostSidebarInput, normaliseSlug, notViaMcp, resolveSurfaceBase, surfacePathFor, toHostSidebarInput, useAuthenticatedQueries, useAuthenticatedQuery, useCurrency, useManagedLifecycle, useMcpUpload, useOrgFormatSync, useOverlayHost, usePluginRealtime, useSurfaceUrl, useToolInvoker, useToolInvokerMap, useTypedToolInvokerMap, useView };
@@ -1,11 +1,12 @@
1
- import { createContext, useEffect, useContext, useState, useMemo, createElement, useRef, useCallback, useSyncExternalStore } from 'react';
1
+ import { createContext, useEffect, useContext, useState, useMemo, createElement, useCallback, useRef, useSyncExternalStore } from 'react';
2
2
  import { QueryClient, useQuery, useQueries, QueryClientProvider } from '@tanstack/react-query';
3
3
  import { definePlatformReactPage, emitNavigation } from '@ethisyscore/components-react';
4
4
  import * as extensionRuntime from '@ethisyscore/extension-runtime/plugin';
5
- import { useHostIdentity, useMcpTool, usePluginRealtimeSource, useExtensionRuntimeTransport } from '@ethisyscore/extension-runtime/plugin';
5
+ import { useHostIdentity, useMcpTool, useOptionalExtensionRuntimeTransport, usePluginRealtimeSource, useExtensionRuntimeTransport } from '@ethisyscore/extension-runtime/plugin';
6
6
  import { useTheme, ThemeProvider } from '@mui/material';
7
7
  import { jsxs, jsx } from 'react/jsx-runtime';
8
8
  import { setOrgFormats } from '@ethisyscore/core-utils/date/org-format';
9
+ import { MONEY_FALLBACK_LOCALE, formatMoney } from '@ethisyscore/core-utils/money';
9
10
 
10
11
  // src/platform-react/definePlatformReactPluginPage.tsx
11
12
  var TemplateContext = createContext(null);
@@ -368,6 +369,89 @@ function useAuthenticatedQuery(options) {
368
369
  function useAuthenticatedQueries(queries) {
369
370
  return useQueries({ queries });
370
371
  }
372
+ var REPORTING_CURRENCY_TOOL = "settings:get-organisation-reporting-currency";
373
+ var LIST_CURRENCIES_TOOL = "settings:list-currencies";
374
+ var emptyCatalogue = () => /* @__PURE__ */ new Map();
375
+ var SETTLED_FULFILLED = "fulfilled";
376
+ function useCurrency() {
377
+ const transport = useOptionalExtensionRuntimeTransport();
378
+ const organisationId = useHostIdentity()?.organisationId ?? null;
379
+ const [reportingCurrency, setReportingCurrency] = useState(null);
380
+ const [currencies, setCurrencies] = useState(emptyCatalogue);
381
+ const [ready, setReady] = useState(false);
382
+ useEffect(() => {
383
+ setReportingCurrency(null);
384
+ setCurrencies(emptyCatalogue());
385
+ setReady(false);
386
+ if (!transport) {
387
+ setReady(true);
388
+ return;
389
+ }
390
+ const controller = new AbortController();
391
+ Promise.allSettled([
392
+ transport.invokeTool(
393
+ REPORTING_CURRENCY_TOOL,
394
+ {},
395
+ controller.signal
396
+ ),
397
+ transport.invokeTool(
398
+ LIST_CURRENCIES_TOOL,
399
+ {},
400
+ controller.signal
401
+ )
402
+ ]).then(([reporting, list]) => {
403
+ if (controller.signal.aborted) return;
404
+ if (reporting.status === SETTLED_FULFILLED) {
405
+ setReportingCurrency(reporting.value?.currency ?? null);
406
+ }
407
+ if (list.status === SETTLED_FULFILLED && Array.isArray(list.value)) {
408
+ setCurrencies(new Map(list.value.map((currency) => [currency.id, currency])));
409
+ }
410
+ setReady(true);
411
+ });
412
+ return () => controller.abort();
413
+ }, [transport, organisationId]);
414
+ const format = useCallback(
415
+ (amount, selector) => {
416
+ const currency = resolveCurrency(selector, currencies, reportingCurrency);
417
+ if (currency === null) {
418
+ return new Intl.NumberFormat(MONEY_FALLBACK_LOCALE).format(amount);
419
+ }
420
+ if (typeof currency === "string") {
421
+ return formatMoney(amount, { currencyCode: currency });
422
+ }
423
+ return formatMoney(amount, {
424
+ currencyCode: currency.code,
425
+ decimalPlaces: currency.decimalPlaces,
426
+ symbol: currency.symbol
427
+ });
428
+ },
429
+ [currencies, reportingCurrency]
430
+ );
431
+ return useMemo(
432
+ () => ({ reportingCurrency, currencies, ready, format }),
433
+ [reportingCurrency, currencies, ready, format]
434
+ );
435
+ }
436
+ function resolveCurrency(selector, currencies, reportingCurrency) {
437
+ if (selector === null || selector === void 0) {
438
+ return reportingCurrency;
439
+ }
440
+ if (typeof selector !== "string") {
441
+ return selector;
442
+ }
443
+ const byId = currencies.get(selector);
444
+ if (byId) {
445
+ return byId;
446
+ }
447
+ const upper = selector.toUpperCase();
448
+ for (const currency of currencies.values()) {
449
+ if (currency.code.toUpperCase() === upper) {
450
+ return currency;
451
+ }
452
+ }
453
+ return selector;
454
+ }
371
455
  function matchRouteParams(pathname, routes, basePath) {
372
456
  const prefix = basePath.length > 1 && basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
373
457
  const norm = pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
@@ -938,6 +1022,6 @@ function menuConfigToHostSidebarInput(config, opts) {
938
1022
  return { mode: "actions", actions };
939
1023
  }
940
1024
 
941
- export { BaseMcpService, DEFAULT_MAX_UPLOAD_BYTES, EHX_PLUGIN_PORTAL_ATTR, EHX_PLUGIN_PORTAL_CLASS, EHX_PLUGIN_ROOT_CLASS, HOST_CHROME_CONTRACT_VERSION, HostSidebarActionsContext, MissingViewFallback, ORGANISATION_SETTINGS_URI, OVERLAY_HOST_CONTRACT_VERSION, OverlayHostContext, PluginPortalScope, PluginStyleScope, SIDEBAR_MODE, SurfaceBaseContext, TemplateContext, TypedMcpService, buildSurfaceUrl, createPluginOverlayDefiner, createPluginPageDefiner, createReactRouterShim, createUseView, definePlatformReactPluginOverlay, definePlatformReactPluginPage, deriveHostEntityToken, injectPluginStyles, isComponent, isHostChromeCompatible, mcpTool, menuConfigToHostSidebarInput, normaliseSlug, notViaMcp, reportHostChromeDiagnostic, resolveSurfaceBase, setHostChromeDiagnosticSink, surfacePathFor, toHostSidebarInput, useAuthenticatedQueries, useAuthenticatedQuery, useHostSidebarActions, useManagedLifecycle, useMcpUpload, useOrgFormatSync, useOverlayHost, usePluginRealtime, useSurfaceUrl, useToolInvoker, useToolInvokerMap, useTypedToolInvokerMap, useView };
1025
+ export { BaseMcpService, DEFAULT_MAX_UPLOAD_BYTES, EHX_PLUGIN_PORTAL_ATTR, EHX_PLUGIN_PORTAL_CLASS, EHX_PLUGIN_ROOT_CLASS, HOST_CHROME_CONTRACT_VERSION, HostSidebarActionsContext, LIST_CURRENCIES_TOOL, MissingViewFallback, ORGANISATION_SETTINGS_URI, OVERLAY_HOST_CONTRACT_VERSION, OverlayHostContext, PluginPortalScope, PluginStyleScope, REPORTING_CURRENCY_TOOL, SIDEBAR_MODE, SurfaceBaseContext, TemplateContext, TypedMcpService, buildSurfaceUrl, createPluginOverlayDefiner, createPluginPageDefiner, createReactRouterShim, createUseView, definePlatformReactPluginOverlay, definePlatformReactPluginPage, deriveHostEntityToken, injectPluginStyles, isComponent, isHostChromeCompatible, mcpTool, menuConfigToHostSidebarInput, normaliseSlug, notViaMcp, reportHostChromeDiagnostic, resolveSurfaceBase, setHostChromeDiagnosticSink, surfacePathFor, toHostSidebarInput, useAuthenticatedQueries, useAuthenticatedQuery, useCurrency, useHostSidebarActions, useManagedLifecycle, useMcpUpload, useOrgFormatSync, useOverlayHost, usePluginRealtime, useSurfaceUrl, useToolInvoker, useToolInvokerMap, useTypedToolInvokerMap, useView };
942
1026
  //# sourceMappingURL=index.js.map
943
1027
  //# sourceMappingURL=index.js.map