@voyant-travel/workflows-react 0.121.0 → 0.122.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.
@@ -24,6 +24,6 @@ export declare function PayloadBlock({ title, value, messages, hideTitle, }: {
24
24
  messages: WorkflowRunsUiMessages;
25
25
  hideTitle?: boolean;
26
26
  }): import("react").JSX.Element;
27
- export declare function formatRelative(iso: string, messages: WorkflowRunsUiMessages): string;
27
+ export declare function formatRelative(iso: string, messages: WorkflowRunsUiMessages, locale: string): string;
28
28
  export declare function formatDuration(ms: number): string;
29
29
  export declare function runHasLiveStatus(run: WorkflowRun | WorkflowRunStep): boolean;
@@ -78,11 +78,11 @@ export function PayloadBlock({ title, value, messages, hideTitle = false, }) {
78
78
  };
79
79
  return (_jsxs("div", { className: "space-y-1", children: [hideTitle ? null : (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-muted-foreground text-xs", children: title }), _jsxs("button", { type: "button", onClick: onCopy, className: "flex items-center gap-1 text-muted-foreground text-xs hover:text-foreground", children: [_jsx(Copy, { className: "h-3 w-3" }), copied ? messages.detail.copied : messages.detail.copy] })] })), _jsx("pre", { className: "max-h-[24rem] overflow-auto rounded bg-muted/40 p-3 font-mono text-[11px] leading-relaxed", children: json })] }));
80
80
  }
81
- export function formatRelative(iso, messages) {
81
+ export function formatRelative(iso, messages, locale) {
82
82
  const seconds = Math.round((Date.now() - new Date(iso).getTime()) / 1000);
83
83
  if (Math.abs(seconds) < 5)
84
84
  return messages.format.relativeNow;
85
- const formatter = new Intl.RelativeTimeFormat(undefined, { numeric: "auto" });
85
+ const formatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
86
86
  if (Math.abs(seconds) < 60)
87
87
  return formatter.format(-seconds, "second");
88
88
  const minutes = Math.round(seconds / 60);
@@ -4,7 +4,7 @@ import { Badge } from "@voyant-travel/ui/components/badge";
4
4
  import { Card, CardContent, CardHeader, CardTitle } from "@voyant-travel/ui/components/card";
5
5
  import { ChevronDown, ChevronRight, Link2 } from "lucide-react";
6
6
  import { useEffect, useMemo, useState } from "react";
7
- import { useWorkflowRunsUiMessagesOrDefault } from "../i18n/index.js";
7
+ import { useWorkflowRunsUiI18nOrDefault, useWorkflowRunsUiMessagesOrDefault, } from "../i18n/index.js";
8
8
  import { CopyableId, formatDuration, formatRelative, PayloadBlock, StatusBadge, StatusIcon, StepStatusIcon, TagChip, } from "./common.js";
9
9
  import { WorkflowRunActionsCard } from "./workflow-run-actions.js";
10
10
  export function WorkflowRunDetailPage({ api, runId, onOpenRun, pollIntervalMs = 3000, className, }) {
@@ -53,8 +53,8 @@ export function WorkflowRunDetailPage({ api, runId, onOpenRun, pollIntervalMs =
53
53
  return (_jsxs("div", { className: `space-y-4 ${className ?? ""}`, children: [_jsx(RunHeaderCard, { run: run, onOpenRun: onOpenRun }), _jsx(WorkflowRunActionsCard, { api: api, run: run, onOpenRun: onOpenRun }), reruns.length > 0 ? _jsx(RerunsListCard, { reruns: reruns, onOpenRun: onOpenRun }) : null, _jsx(StepsCard, { steps: steps }), run.input ? _jsx(PayloadCard, { title: messages.detail.input, value: run.input }) : null, run.result ? _jsx(PayloadCard, { title: messages.detail.result, value: run.result }) : null, run.error && !duplicateRunError ? (_jsx(ErrorCard, { title: messages.detail.runError, error: run.error })) : null] }));
54
54
  }
55
55
  function RunHeaderCard({ run, onOpenRun }) {
56
- const messages = useWorkflowRunsUiMessagesOrDefault();
57
- return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "space-y-3 pb-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx(StatusIcon, { status: run.status }), _jsx(CardTitle, { className: "font-semibold text-lg", children: run.workflowName }), _jsx(StatusBadge, { status: run.status, messages: messages }), run.durationMs != null ? (_jsx(Badge, { variant: "outline", className: "font-mono text-xs", children: formatDuration(run.durationMs) })) : null, run.resumeFromStep ? (_jsx(Badge, { variant: "outline", className: "font-mono text-xs", children: messages.detail.resumedAt(run.resumeFromStep) })) : null, _jsx(CopyableId, { id: run.id, copiedLabel: messages.detail.copied, className: "ml-auto" })] }), _jsxs("div", { className: "text-muted-foreground text-sm", children: [_jsxs("span", { children: [messages.detail.started, " ", new Date(run.startedAt).toLocaleString()] }), run.completedAt ? (_jsxs("span", { children: [" · ", messages.detail.finished, " ", new Date(run.completedAt).toLocaleString()] })) : null] })] }), _jsxs(CardContent, { className: "space-y-3 pt-0", children: [_jsxs("dl", { className: "grid grid-cols-1 gap-x-6 gap-y-2 text-sm sm:grid-cols-2", children: [_jsx(DefRow, { label: messages.detail.trigger, value: run.trigger, mono: true }), run.correlationId ? (_jsx(DefRow, { label: messages.detail.correlation, value: run.correlationId, mono: true })) : null, run.parentRunId ? (_jsx(LinkedRunRow, { label: messages.detail.parent, runId: run.parentRunId, onOpenRun: onOpenRun })) : null, run.triggeredByUserId ? (_jsx(DefRow, { label: messages.detail.triggeredBy, value: run.triggeredByUserId, mono: true })) : null] }), run.tags.length > 0 ? (_jsxs("div", { children: [_jsx("div", { className: "mb-1.5 text-muted-foreground text-xs uppercase tracking-wide", children: messages.detail.tags }), _jsx("div", { className: "flex flex-wrap gap-1", children: run.tags.map((tag) => (_jsx(TagChip, { tag: tag }, tag))) })] })) : null] })] }));
56
+ const { formatDateTime, messages } = useWorkflowRunsUiI18nOrDefault();
57
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "space-y-3 pb-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx(StatusIcon, { status: run.status }), _jsx(CardTitle, { className: "font-semibold text-lg", children: run.workflowName }), _jsx(StatusBadge, { status: run.status, messages: messages }), run.durationMs != null ? (_jsx(Badge, { variant: "outline", className: "font-mono text-xs", children: formatDuration(run.durationMs) })) : null, run.resumeFromStep ? (_jsx(Badge, { variant: "outline", className: "font-mono text-xs", children: messages.detail.resumedAt(run.resumeFromStep) })) : null, _jsx(CopyableId, { id: run.id, copiedLabel: messages.detail.copied, className: "ml-auto" })] }), _jsxs("div", { className: "text-muted-foreground text-sm", children: [_jsxs("span", { children: [messages.detail.started, " ", formatDateTime(run.startedAt)] }), run.completedAt ? (_jsxs("span", { children: [" · ", messages.detail.finished, " ", formatDateTime(run.completedAt)] })) : null] })] }), _jsxs(CardContent, { className: "space-y-3 pt-0", children: [_jsxs("dl", { className: "grid grid-cols-1 gap-x-6 gap-y-2 text-sm sm:grid-cols-2", children: [_jsx(DefRow, { label: messages.detail.trigger, value: run.trigger, mono: true }), run.correlationId ? (_jsx(DefRow, { label: messages.detail.correlation, value: run.correlationId, mono: true })) : null, run.parentRunId ? (_jsx(LinkedRunRow, { label: messages.detail.parent, runId: run.parentRunId, onOpenRun: onOpenRun })) : null, run.triggeredByUserId ? (_jsx(DefRow, { label: messages.detail.triggeredBy, value: run.triggeredByUserId, mono: true })) : null] }), run.tags.length > 0 ? (_jsxs("div", { children: [_jsx("div", { className: "mb-1.5 text-muted-foreground text-xs uppercase tracking-wide", children: messages.detail.tags }), _jsx("div", { className: "flex flex-wrap gap-1", children: run.tags.map((tag) => (_jsx(TagChip, { tag: tag }, tag))) })] })) : null] })] }));
58
58
  }
59
59
  function LinkedRunRow({ label, runId, onOpenRun, }) {
60
60
  return (_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("dt", { className: "shrink-0 text-muted-foreground text-xs uppercase tracking-wide", children: label }), _jsx("dd", { className: "min-w-0 truncate", children: _jsxs("button", { type: "button", onClick: () => onOpenRun?.(runId), className: "inline-flex items-center gap-1.5 truncate font-mono text-xs hover:underline", title: runId, children: [_jsx(Link2, { className: "h-3 w-3 opacity-60" }), runId] }) })] }));
@@ -64,8 +64,8 @@ function DefRow({ label, value, mono }) {
64
64
  , children: value })] }));
65
65
  }
66
66
  function RerunsListCard({ reruns, onOpenRun, }) {
67
- const messages = useWorkflowRunsUiMessagesOrDefault();
68
- return (_jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs(CardTitle, { className: "flex items-center gap-2 text-sm", children: [_jsx(Link2, { className: "h-3.5 w-3.5 text-muted-foreground" }), messages.detail.reruns, _jsx("span", { className: "font-normal text-muted-foreground text-xs", children: `(${reruns.length})` })] }) }), _jsx(CardContent, { children: _jsx("ul", { className: "space-y-1", children: reruns.map((run) => (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => onOpenRun?.(run.id), className: "flex w-full items-center gap-2 rounded p-2 text-left text-sm hover:bg-muted/40", children: [_jsx(StatusIcon, { status: run.status }), _jsx("span", { className: "font-mono text-xs", children: run.id }), _jsx("span", { className: "text-muted-foreground text-xs", children: run.trigger }), run.resumeFromStep ? (_jsx(Badge, { variant: "outline", className: "font-mono text-[10px]", children: messages.detail.resumedAt(run.resumeFromStep) })) : null, _jsx("span", { className: "ml-auto whitespace-nowrap text-muted-foreground text-xs", children: formatRelative(run.startedAt, messages) })] }) }, run.id))) }) })] }));
67
+ const { locale, messages } = useWorkflowRunsUiI18nOrDefault();
68
+ return (_jsxs(Card, { children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs(CardTitle, { className: "flex items-center gap-2 text-sm", children: [_jsx(Link2, { className: "h-3.5 w-3.5 text-muted-foreground" }), messages.detail.reruns, _jsx("span", { className: "font-normal text-muted-foreground text-xs", children: `(${reruns.length})` })] }) }), _jsx(CardContent, { children: _jsx("ul", { className: "space-y-1", children: reruns.map((run) => (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => onOpenRun?.(run.id), className: "flex w-full items-center gap-2 rounded p-2 text-left text-sm hover:bg-muted/40", children: [_jsx(StatusIcon, { status: run.status }), _jsx("span", { className: "font-mono text-xs", children: run.id }), _jsx("span", { className: "text-muted-foreground text-xs", children: run.trigger }), run.resumeFromStep ? (_jsx(Badge, { variant: "outline", className: "font-mono text-[10px]", children: messages.detail.resumedAt(run.resumeFromStep) })) : null, _jsx("span", { className: "ml-auto whitespace-nowrap text-muted-foreground text-xs", children: formatRelative(run.startedAt, messages, locale) })] }) }, run.id))) }) })] }));
69
69
  }
70
70
  function StepsCard({ steps }) {
71
71
  const messages = useWorkflowRunsUiMessagesOrDefault();
@@ -4,7 +4,7 @@ import { Button } from "@voyant-travel/ui/components/button";
4
4
  import { Card, CardContent } from "@voyant-travel/ui/components/card";
5
5
  import { Clock, Workflow } from "lucide-react";
6
6
  import { useEffect, useMemo, useState } from "react";
7
- import { useWorkflowRunsUiMessagesOrDefault } from "../i18n/index.js";
7
+ import { useWorkflowRunsUiI18nOrDefault, useWorkflowRunsUiMessagesOrDefault, } from "../i18n/index.js";
8
8
  import { formatDuration, formatRelative, StatusBadge, StatusIcon, TagChip } from "./common.js";
9
9
  import { WorkflowRunDetailPage } from "./workflow-run-detail-page.js";
10
10
  import { buildFilterOptions, WorkflowRunsFilters } from "./workflow-runs-filters.js";
@@ -84,9 +84,9 @@ export function WorkflowRunsPageSkeleton() {
84
84
  return (_jsxs("div", { className: "flex min-h-screen flex-col bg-background", children: [_jsx("header", { className: "border-b px-4 py-3", children: _jsx("div", { className: "h-5 w-40 rounded bg-muted", role: "status", "aria-label": messages.page.loading }) }), _jsxs("main", { className: "container mx-auto flex flex-1 flex-col gap-6 px-4 py-6 md:flex-row", children: [_jsxs("aside", { className: "space-y-3 md:w-96 md:shrink-0", children: [_jsx("div", { className: "h-56 rounded-md bg-muted" }), _jsx("div", { className: "h-20 rounded-md bg-muted" }), _jsx("div", { className: "h-20 rounded-md bg-muted" })] }), _jsx("section", { className: "min-h-[24rem] flex-1 rounded-md bg-muted" })] })] }));
85
85
  }
86
86
  function RunListItem({ run, selected, activeTagFilters, activeStatusFilters, onSelect, onToggleStatus, onToggleTag, }) {
87
- const messages = useWorkflowRunsUiMessagesOrDefault();
87
+ const { locale, messages } = useWorkflowRunsUiI18nOrDefault();
88
88
  return (_jsxs("div", { className: `rounded-md border bg-card p-3 text-sm transition-colors hover:bg-muted/50 ${selected ? "border-primary bg-primary/5" : "" // i18n-literal-ok: CSS classes
89
- }`, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("button", { type: "button", onClick: onSelect, className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [_jsx(StatusIcon, { status: run.status }), _jsx("span", { className: "truncate font-medium", children: run.workflowName }), _jsx("span", { className: "ml-auto whitespace-nowrap text-muted-foreground text-xs", children: formatRelative(run.startedAt, messages) })] }), _jsx("button", { type: "button", onClick: () => onToggleStatus(run.status), "aria-pressed": activeStatusFilters.includes(run.status), children: _jsx(StatusBadge, { status: run.status, messages: messages }) })] }), run.durationMs != null ? (_jsx("div", { className: "mt-1 text-muted-foreground text-xs", children: formatDuration(run.durationMs) })) : null, run.tags.length > 0 ? (_jsxs("div", { className: "mt-2 flex flex-wrap gap-1", children: [run.tags.slice(0, 3).map((tag) => (_jsx("button", { type: "button", onClick: () => onToggleTag(tag), "aria-pressed": activeTagFilters.includes(tag), className: activeTagFilters.includes(tag) ? "rounded-full ring-2 ring-primary/40" : undefined, children: _jsx(TagChip, { tag: tag }) }, tag))), run.tags.length > 3 ? (_jsx("span", { className: "text-muted-foreground text-xs", children: `+${run.tags.length - 3}` })) : null] })) : null] }));
89
+ }`, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("button", { type: "button", onClick: onSelect, className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [_jsx(StatusIcon, { status: run.status }), _jsx("span", { className: "truncate font-medium", children: run.workflowName }), _jsx("span", { className: "ml-auto whitespace-nowrap text-muted-foreground text-xs", children: formatRelative(run.startedAt, messages, locale) })] }), _jsx("button", { type: "button", onClick: () => onToggleStatus(run.status), "aria-pressed": activeStatusFilters.includes(run.status), children: _jsx(StatusBadge, { status: run.status, messages: messages }) })] }), run.durationMs != null ? (_jsx("div", { className: "mt-1 text-muted-foreground text-xs", children: formatDuration(run.durationMs) })) : null, run.tags.length > 0 ? (_jsxs("div", { className: "mt-2 flex flex-wrap gap-1", children: [run.tags.slice(0, 3).map((tag) => (_jsx("button", { type: "button", onClick: () => onToggleTag(tag), "aria-pressed": activeTagFilters.includes(tag), className: activeTagFilters.includes(tag) ? "rounded-full ring-2 ring-primary/40" : undefined, children: _jsx(TagChip, { tag: tag }) }, tag))), run.tags.length > 3 ? (_jsx("span", { className: "text-muted-foreground text-xs", children: `+${run.tags.length - 3}` })) : null] })) : null] }));
90
90
  }
91
91
  function SelectPrompt() {
92
92
  const messages = useWorkflowRunsUiMessagesOrDefault();
@@ -5,10 +5,10 @@ import { Button } from "@voyant-travel/ui/components/button";
5
5
  import { Card, CardContent } from "@voyant-travel/ui/components/card";
6
6
  import { AlertTriangle, CalendarClock, RefreshCw } from "lucide-react";
7
7
  import { useCallback, useEffect, useMemo, useState } from "react";
8
- import { useWorkflowRunsUiMessagesOrDefault } from "../i18n/index.js";
8
+ import { useWorkflowRunsUiI18nOrDefault } from "../i18n/index.js";
9
9
  import { formatRelative, StatusIcon } from "./common.js";
10
10
  export function WorkflowSchedulesPage({ schedulesApi, runsApi, onTriggerNow, environment = "production", pollIntervalMs = 30_000, className, }) {
11
- const rootMessages = useWorkflowRunsUiMessagesOrDefault();
11
+ const { locale, messages: rootMessages } = useWorkflowRunsUiI18nOrDefault();
12
12
  const messages = rootMessages.schedules;
13
13
  const [response, setResponse] = useState(null);
14
14
  const [lastRuns, setLastRuns] = useState({});
@@ -76,11 +76,11 @@ export function WorkflowSchedulesPage({ schedulesApi, runsApi, onTriggerNow, env
76
76
  ? "border-emerald-500/40 bg-emerald-500/5"
77
77
  : "border-destructive/40 bg-destructive/5", children: _jsx(CardContent, { className: `pt-4 text-sm ${triggerNotice.kind === "success"
78
78
  ? "text-emerald-600 dark:text-emerald-300"
79
- : "text-destructive"}`, children: triggerNotice.text }) })) : null, error ? (_jsx(Card, { className: "border-destructive/40", children: _jsx(CardContent, { className: "pt-4 text-destructive text-sm", children: error }) })) : null, !error && rows.length === 0 && !loading ? (_jsx(Card, { children: _jsx(CardContent, { className: "pt-4 text-muted-foreground text-sm", children: messages.empty }) })) : null, rows.length > 0 ? (_jsx("div", { className: "overflow-hidden rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/40 text-left text-xs uppercase", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 font-medium", children: messages.workflowColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.scheduleColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.nextRunColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.lastRunColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.statusColumn }), onTriggerNow ? (_jsx("th", { className: "px-3 py-2 font-medium", children: messages.actionsColumn })) : null] }) }), _jsx("tbody", { children: rows.map((row) => (_jsx(ScheduleRow, { row: row, lastRun: lastRuns[row.workflowId] ?? null, triggering: !!triggering[row.scheduleId], onTriggerNow: onTriggerNow ? () => void triggerRow(row) : undefined, envFlagDisabled: showEnvFlag && !envFlagOn, rootMessages: rootMessages }, row.scheduleId))) })] }) })) : null] })] }));
79
+ : "text-destructive"}`, children: triggerNotice.text }) })) : null, error ? (_jsx(Card, { className: "border-destructive/40", children: _jsx(CardContent, { className: "pt-4 text-destructive text-sm", children: error }) })) : null, !error && rows.length === 0 && !loading ? (_jsx(Card, { children: _jsx(CardContent, { className: "pt-4 text-muted-foreground text-sm", children: messages.empty }) })) : null, rows.length > 0 ? (_jsx("div", { className: "overflow-hidden rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/40 text-left text-xs uppercase", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 font-medium", children: messages.workflowColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.scheduleColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.nextRunColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.lastRunColumn }), _jsx("th", { className: "px-3 py-2 font-medium", children: messages.statusColumn }), onTriggerNow ? (_jsx("th", { className: "px-3 py-2 font-medium", children: messages.actionsColumn })) : null] }) }), _jsx("tbody", { children: rows.map((row) => (_jsx(ScheduleRow, { row: row, lastRun: lastRuns[row.workflowId] ?? null, triggering: !!triggering[row.scheduleId], onTriggerNow: onTriggerNow ? () => void triggerRow(row) : undefined, envFlagDisabled: showEnvFlag && !envFlagOn, rootMessages: rootMessages, locale: locale }, row.scheduleId))) })] }) })) : null] })] }));
80
80
  }
81
- function ScheduleRow({ row, lastRun, triggering, onTriggerNow, envFlagDisabled, rootMessages, }) {
81
+ function ScheduleRow({ row, lastRun, triggering, onTriggerNow, envFlagDisabled, rootMessages, locale, }) {
82
82
  const messages = rootMessages.schedules;
83
- return (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "px-3 py-2 font-mono text-xs", children: row.workflowId }), _jsx("td", { className: "px-3 py-2 font-mono text-xs", children: formatScheduleDecl(row.schedule, messages) }), _jsx("td", { className: "px-3 py-2 text-xs text-muted-foreground", children: formatNextRun(row, messages, rootMessages) }), _jsx("td", { className: "px-3 py-2 text-xs", children: formatLastRun(lastRun, row, messages, rootMessages) }), _jsx("td", { className: "px-3 py-2", children: _jsx(StatusPill, { row: row, envFlagDisabled: envFlagDisabled, messages: messages }) }), onTriggerNow ? (_jsx("td", { className: "px-3 py-2", children: _jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: onTriggerNow, disabled: triggering, children: triggering ? messages.triggering : messages.triggerNow }) })) : null] }));
83
+ return (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "px-3 py-2 font-mono text-xs", children: row.workflowId }), _jsx("td", { className: "px-3 py-2 font-mono text-xs", children: formatScheduleDecl(row.schedule, messages) }), _jsx("td", { className: "px-3 py-2 text-xs text-muted-foreground", children: formatNextRun(row, messages, rootMessages, locale) }), _jsx("td", { className: "px-3 py-2 text-xs", children: formatLastRun(lastRun, row, messages, rootMessages, locale) }), _jsx("td", { className: "px-3 py-2", children: _jsx(StatusPill, { row: row, envFlagDisabled: envFlagDisabled, messages: messages }) }), onTriggerNow ? (_jsx("td", { className: "px-3 py-2", children: _jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: onTriggerNow, disabled: triggering, children: triggering ? messages.triggering : messages.triggerNow }) })) : null] }));
84
84
  }
85
85
  function StatusPill({ row, envFlagDisabled, messages, }) {
86
86
  if (envFlagDisabled) {
@@ -103,29 +103,29 @@ function formatScheduleDecl(decl, messages) {
103
103
  return messages.at(decl.at);
104
104
  return messages.eventDriven;
105
105
  }
106
- function formatNextRun(row, messages, rootMessages) {
106
+ function formatNextRun(row, messages, rootMessages, locale) {
107
107
  if (!row.enabled || row.nextRunAt === null)
108
108
  return messages.notScheduled;
109
109
  const delta = row.nextRunAt - Date.now();
110
- const relative = formatRelative(new Date(row.nextRunAt).toISOString(), rootMessages);
110
+ const relative = formatRelative(new Date(row.nextRunAt).toISOString(), rootMessages, locale);
111
111
  return delta >= 0 ? messages.inFuture(relative) : messages.inPast(relative);
112
112
  }
113
- function formatLastRun(lastRun, row, messages, rootMessages) {
113
+ function formatLastRun(lastRun, row, messages, rootMessages, locale) {
114
114
  if (lastRun) {
115
- const relative = formatRelative(lastRun.startedAt, rootMessages);
115
+ const relative = formatRelative(lastRun.startedAt, rootMessages, locale);
116
116
  const label = lastRunLabel(lastRun.status, relative, messages);
117
117
  return (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(StatusIcon, { status: lastRun.status }), label] }));
118
118
  }
119
119
  if (row.lastError && row.lastFireAt !== undefined && row.lastFireAt !== null) {
120
- const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages);
120
+ const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages, locale);
121
121
  return (_jsxs("span", { className: "inline-flex items-center gap-1.5", title: row.lastError, children: [_jsx(StatusIcon, { status: "failed" }), messages.lastRunFailed(relative)] }));
122
122
  }
123
123
  if (row.lastSuccessfulRunAt !== undefined && row.lastSuccessfulRunAt !== null) {
124
- const relative = formatRelative(new Date(row.lastSuccessfulRunAt).toISOString(), rootMessages);
124
+ const relative = formatRelative(new Date(row.lastSuccessfulRunAt).toISOString(), rootMessages, locale);
125
125
  return (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(StatusIcon, { status: "succeeded" }), messages.lastRunSucceeded(relative)] }));
126
126
  }
127
127
  if (row.lastFireAt !== undefined && row.lastFireAt !== null) {
128
- const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages);
128
+ const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages, locale);
129
129
  return (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(CalendarClock, { className: "h-3.5 w-3.5 text-muted-foreground", "aria-hidden": "true" }), messages.lastFireRecorded(relative)] }));
130
130
  }
131
131
  return _jsx("span", { className: "text-muted-foreground", children: messages.lastRunNone });
@@ -14,9 +14,10 @@ export declare function getWorkflowRunsUiI18n({ locale, overrides, }: {
14
14
  locale?: string | null | undefined;
15
15
  overrides?: WorkflowRunsUiMessageOverrides | null;
16
16
  }): PackageI18nValue<WorkflowRunsUiMessages>;
17
- export declare function WorkflowRunsUiMessagesProvider({ children, locale, overrides, }: {
17
+ export declare function WorkflowRunsUiMessagesProvider({ children, locale, timeZone, overrides, }: {
18
18
  children: ReactNode;
19
19
  locale: string | null | undefined;
20
+ timeZone?: string | null;
20
21
  overrides?: WorkflowRunsUiMessageOverrides | null;
21
22
  }): import("react").JSX.Element;
22
23
  export declare const useWorkflowRunsUiI18n: () => PackageI18nValue<WorkflowRunsUiMessages>;
@@ -31,8 +31,8 @@ export function getWorkflowRunsUiI18n({ locale, overrides, }) {
31
31
  ...createLocaleFormatters(resolvedLocale),
32
32
  };
33
33
  }
34
- export function WorkflowRunsUiMessagesProvider({ children, locale, overrides, }) {
35
- return (_jsx(workflowRunsUiContext.ResolvedMessagesProvider, { definitions: workflowRunsUiMessageDefinitions, fallbackLocale: fallbackLocale, locale: locale, overrides: overrides, children: children }));
34
+ export function WorkflowRunsUiMessagesProvider({ children, locale, timeZone, overrides, }) {
35
+ return (_jsx(workflowRunsUiContext.ResolvedMessagesProvider, { definitions: workflowRunsUiMessageDefinitions, fallbackLocale: fallbackLocale, locale: locale, timeZone: timeZone, overrides: overrides, children: children }));
36
36
  }
37
37
  export const useWorkflowRunsUiI18n = workflowRunsUiContext.useI18n;
38
38
  export const useWorkflowRunsUiMessages = workflowRunsUiContext.useMessages;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyant-travel/workflows-react",
3
- "version": "0.121.0",
3
+ "version": "0.122.1",
4
4
  "description": "React hooks for Voyant Workflows — trigger, subscribe, and stream runs — plus the styled workflow-run admin UI (formerly @voyant-travel/workflows-ui).",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -66,9 +66,9 @@
66
66
  }
67
67
  },
68
68
  "dependencies": {
69
- "@voyant-travel/i18n": "^0.111.1",
70
- "@voyant-travel/workflows": "^0.121.0",
71
- "@voyant-travel/react": "^0.104.2"
69
+ "@voyant-travel/i18n": "^0.111.3",
70
+ "@voyant-travel/react": "^0.104.2",
71
+ "@voyant-travel/workflows": "^0.122.1"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "@tanstack/react-query": "^5.0.0",
@@ -94,7 +94,7 @@
94
94
  "react-dom": "^19.2.7",
95
95
  "typescript": "^6.0.3",
96
96
  "vitest": "^4.1.9",
97
- "@voyant-travel/i18n": "^0.111.1",
97
+ "@voyant-travel/i18n": "^0.111.3",
98
98
  "@voyant-travel/react": "^0.104.2",
99
99
  "@voyant-travel/ui": "^0.109.2",
100
100
  "@voyant-travel/voyant-typescript-config": "^0.1.0"
@@ -156,10 +156,14 @@ export function PayloadBlock({
156
156
  )
157
157
  }
158
158
 
159
- export function formatRelative(iso: string, messages: WorkflowRunsUiMessages): string {
159
+ export function formatRelative(
160
+ iso: string,
161
+ messages: WorkflowRunsUiMessages,
162
+ locale: string,
163
+ ): string {
160
164
  const seconds = Math.round((Date.now() - new Date(iso).getTime()) / 1000)
161
165
  if (Math.abs(seconds) < 5) return messages.format.relativeNow
162
- const formatter = new Intl.RelativeTimeFormat(undefined, { numeric: "auto" })
166
+ const formatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" })
163
167
  if (Math.abs(seconds) < 60) return formatter.format(-seconds, "second")
164
168
  const minutes = Math.round(seconds / 60)
165
169
  if (Math.abs(minutes) < 60) return formatter.format(-minutes, "minute")
@@ -5,7 +5,10 @@ import { Card, CardContent, CardHeader, CardTitle } from "@voyant-travel/ui/comp
5
5
  import { ChevronDown, ChevronRight, Link2 } from "lucide-react"
6
6
  import { useEffect, useMemo, useState } from "react"
7
7
 
8
- import { useWorkflowRunsUiMessagesOrDefault } from "../i18n/index.js"
8
+ import {
9
+ useWorkflowRunsUiI18nOrDefault,
10
+ useWorkflowRunsUiMessagesOrDefault,
11
+ } from "../i18n/index.js"
9
12
  import type {
10
13
  WorkflowRun,
11
14
  WorkflowRunErrorPayload,
@@ -102,7 +105,7 @@ export function WorkflowRunDetailPage({
102
105
  }
103
106
 
104
107
  function RunHeaderCard({ run, onOpenRun }: { run: WorkflowRun; onOpenRun?: (id: string) => void }) {
105
- const messages = useWorkflowRunsUiMessagesOrDefault()
108
+ const { formatDateTime, messages } = useWorkflowRunsUiI18nOrDefault()
106
109
  return (
107
110
  <Card>
108
111
  <CardHeader className="space-y-3 pb-4">
@@ -124,12 +127,12 @@ function RunHeaderCard({ run, onOpenRun }: { run: WorkflowRun; onOpenRun?: (id:
124
127
  </div>
125
128
  <div className="text-muted-foreground text-sm">
126
129
  <span>
127
- {messages.detail.started} {new Date(run.startedAt).toLocaleString()}
130
+ {messages.detail.started} {formatDateTime(run.startedAt)}
128
131
  </span>
129
132
  {run.completedAt ? (
130
133
  <span>
131
134
  {" · "}
132
- {messages.detail.finished} {new Date(run.completedAt).toLocaleString()}
135
+ {messages.detail.finished} {formatDateTime(run.completedAt)}
133
136
  </span>
134
137
  ) : null}
135
138
  </div>
@@ -217,7 +220,7 @@ function RerunsListCard({
217
220
  reruns: WorkflowRun[]
218
221
  onOpenRun?: (id: string) => void
219
222
  }) {
220
- const messages = useWorkflowRunsUiMessagesOrDefault()
223
+ const { locale, messages } = useWorkflowRunsUiI18nOrDefault()
221
224
  return (
222
225
  <Card>
223
226
  <CardHeader className="pb-3">
@@ -245,7 +248,7 @@ function RerunsListCard({
245
248
  </Badge>
246
249
  ) : null}
247
250
  <span className="ml-auto whitespace-nowrap text-muted-foreground text-xs">
248
- {formatRelative(run.startedAt, messages)}
251
+ {formatRelative(run.startedAt, messages, locale)}
249
252
  </span>
250
253
  </button>
251
254
  </li>
@@ -5,7 +5,10 @@ import { Card, CardContent } from "@voyant-travel/ui/components/card"
5
5
  import { Clock, Workflow } from "lucide-react"
6
6
  import { useEffect, useMemo, useState } from "react"
7
7
 
8
- import { useWorkflowRunsUiMessagesOrDefault } from "../i18n/index.js"
8
+ import {
9
+ useWorkflowRunsUiI18nOrDefault,
10
+ useWorkflowRunsUiMessagesOrDefault,
11
+ } from "../i18n/index.js"
9
12
  import type {
10
13
  ListWorkflowRunsQuery,
11
14
  WorkflowRun,
@@ -241,7 +244,7 @@ function RunListItem({
241
244
  onToggleStatus: (status: WorkflowRunStatus) => void
242
245
  onToggleTag: (tag: string) => void
243
246
  }) {
244
- const messages = useWorkflowRunsUiMessagesOrDefault()
247
+ const { locale, messages } = useWorkflowRunsUiI18nOrDefault()
245
248
  return (
246
249
  <div
247
250
  className={`rounded-md border bg-card p-3 text-sm transition-colors hover:bg-muted/50 ${
@@ -257,7 +260,7 @@ function RunListItem({
257
260
  <StatusIcon status={run.status} />
258
261
  <span className="truncate font-medium">{run.workflowName}</span>
259
262
  <span className="ml-auto whitespace-nowrap text-muted-foreground text-xs">
260
- {formatRelative(run.startedAt, messages)}
263
+ {formatRelative(run.startedAt, messages, locale)}
261
264
  </span>
262
265
  </button>
263
266
  <button
@@ -6,7 +6,7 @@ import { Card, CardContent } from "@voyant-travel/ui/components/card"
6
6
  import { AlertTriangle, CalendarClock, RefreshCw } from "lucide-react"
7
7
  import { useCallback, useEffect, useMemo, useState } from "react"
8
8
 
9
- import { useWorkflowRunsUiMessagesOrDefault, type WorkflowRunsUiMessages } from "../i18n/index.js"
9
+ import { useWorkflowRunsUiI18nOrDefault, type WorkflowRunsUiMessages } from "../i18n/index.js"
10
10
  import type {
11
11
  ListWorkflowSchedulesResponse,
12
12
  WorkflowScheduleDecl,
@@ -44,7 +44,7 @@ export function WorkflowSchedulesPage({
44
44
  pollIntervalMs = 30_000,
45
45
  className,
46
46
  }: WorkflowSchedulesPageProps) {
47
- const rootMessages = useWorkflowRunsUiMessagesOrDefault()
47
+ const { locale, messages: rootMessages } = useWorkflowRunsUiI18nOrDefault()
48
48
  const messages = rootMessages.schedules
49
49
  const [response, setResponse] = useState<ListWorkflowSchedulesResponse | null>(null)
50
50
  const [lastRuns, setLastRuns] = useState<Record<string, WorkflowRun | null>>({})
@@ -220,6 +220,7 @@ export function WorkflowSchedulesPage({
220
220
  onTriggerNow={onTriggerNow ? () => void triggerRow(row) : undefined}
221
221
  envFlagDisabled={showEnvFlag && !envFlagOn}
222
222
  rootMessages={rootMessages}
223
+ locale={locale}
223
224
  />
224
225
  ))}
225
226
  </tbody>
@@ -238,6 +239,7 @@ function ScheduleRow({
238
239
  onTriggerNow,
239
240
  envFlagDisabled,
240
241
  rootMessages,
242
+ locale,
241
243
  }: {
242
244
  row: WorkflowScheduleSummary
243
245
  lastRun: WorkflowRun | null
@@ -245,6 +247,7 @@ function ScheduleRow({
245
247
  onTriggerNow?: () => void
246
248
  envFlagDisabled: boolean
247
249
  rootMessages: WorkflowRunsUiMessages
250
+ locale: string
248
251
  }) {
249
252
  const messages = rootMessages.schedules
250
253
 
@@ -253,9 +256,11 @@ function ScheduleRow({
253
256
  <td className="px-3 py-2 font-mono text-xs">{row.workflowId}</td>
254
257
  <td className="px-3 py-2 font-mono text-xs">{formatScheduleDecl(row.schedule, messages)}</td>
255
258
  <td className="px-3 py-2 text-xs text-muted-foreground">
256
- {formatNextRun(row, messages, rootMessages)}
259
+ {formatNextRun(row, messages, rootMessages, locale)}
260
+ </td>
261
+ <td className="px-3 py-2 text-xs">
262
+ {formatLastRun(lastRun, row, messages, rootMessages, locale)}
257
263
  </td>
258
- <td className="px-3 py-2 text-xs">{formatLastRun(lastRun, row, messages, rootMessages)}</td>
259
264
  <td className="px-3 py-2">
260
265
  <StatusPill row={row} envFlagDisabled={envFlagDisabled} messages={messages} />
261
266
  </td>
@@ -327,10 +332,11 @@ function formatNextRun(
327
332
  row: WorkflowScheduleSummary,
328
333
  messages: SchedulesMessages,
329
334
  rootMessages: WorkflowRunsUiMessages,
335
+ locale: string,
330
336
  ): string {
331
337
  if (!row.enabled || row.nextRunAt === null) return messages.notScheduled
332
338
  const delta = row.nextRunAt - Date.now()
333
- const relative = formatRelative(new Date(row.nextRunAt).toISOString(), rootMessages)
339
+ const relative = formatRelative(new Date(row.nextRunAt).toISOString(), rootMessages, locale)
334
340
  return delta >= 0 ? messages.inFuture(relative) : messages.inPast(relative)
335
341
  }
336
342
 
@@ -339,9 +345,10 @@ function formatLastRun(
339
345
  row: WorkflowScheduleSummary,
340
346
  messages: SchedulesMessages,
341
347
  rootMessages: WorkflowRunsUiMessages,
348
+ locale: string,
342
349
  ) {
343
350
  if (lastRun) {
344
- const relative = formatRelative(lastRun.startedAt, rootMessages)
351
+ const relative = formatRelative(lastRun.startedAt, rootMessages, locale)
345
352
  const label = lastRunLabel(lastRun.status, relative, messages)
346
353
  return (
347
354
  <span className="inline-flex items-center gap-1.5">
@@ -351,7 +358,7 @@ function formatLastRun(
351
358
  )
352
359
  }
353
360
  if (row.lastError && row.lastFireAt !== undefined && row.lastFireAt !== null) {
354
- const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages)
361
+ const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages, locale)
355
362
  return (
356
363
  <span className="inline-flex items-center gap-1.5" title={row.lastError}>
357
364
  <StatusIcon status="failed" />
@@ -360,7 +367,11 @@ function formatLastRun(
360
367
  )
361
368
  }
362
369
  if (row.lastSuccessfulRunAt !== undefined && row.lastSuccessfulRunAt !== null) {
363
- const relative = formatRelative(new Date(row.lastSuccessfulRunAt).toISOString(), rootMessages)
370
+ const relative = formatRelative(
371
+ new Date(row.lastSuccessfulRunAt).toISOString(),
372
+ rootMessages,
373
+ locale,
374
+ )
364
375
  return (
365
376
  <span className="inline-flex items-center gap-1.5">
366
377
  <StatusIcon status="succeeded" />
@@ -369,7 +380,7 @@ function formatLastRun(
369
380
  )
370
381
  }
371
382
  if (row.lastFireAt !== undefined && row.lastFireAt !== null) {
372
- const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages)
383
+ const relative = formatRelative(new Date(row.lastFireAt).toISOString(), rootMessages, locale)
373
384
  return (
374
385
  <span className="inline-flex items-center gap-1.5">
375
386
  <CalendarClock className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />
@@ -66,10 +66,12 @@ export function getWorkflowRunsUiI18n({
66
66
  export function WorkflowRunsUiMessagesProvider({
67
67
  children,
68
68
  locale,
69
+ timeZone,
69
70
  overrides,
70
71
  }: {
71
72
  children: ReactNode
72
73
  locale: string | null | undefined
74
+ timeZone?: string | null
73
75
  overrides?: WorkflowRunsUiMessageOverrides | null
74
76
  }) {
75
77
  return (
@@ -77,6 +79,7 @@ export function WorkflowRunsUiMessagesProvider({
77
79
  definitions={workflowRunsUiMessageDefinitions}
78
80
  fallbackLocale={fallbackLocale}
79
81
  locale={locale}
82
+ timeZone={timeZone}
80
83
  overrides={overrides}
81
84
  >
82
85
  {children}