@burdenoff/fluidgrids-fe-sdk 2026.1108.2 → 2026.1108.4

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.
@@ -1,7 +1,7 @@
1
- export { WorkflowBuilder } from './chunk-N2QXEV5Y.js';
1
+ export { WorkflowBuilder } from './chunk-CRTZL55P.js';
2
2
  import './chunk-7XW6BC4B.js';
3
3
  import './chunk-PIKYQE7P.js';
4
- import './chunk-66AKFXP7.js';
4
+ import './chunk-MJ5EI5OT.js';
5
5
  import './chunk-E7KIH3CF.js';
6
6
  import './chunk-GEMNVFPK.js';
7
7
  import './chunk-B6CCE2RW.js';
@@ -1,6 +1,6 @@
1
1
  import { isGuidedTourIntent, pickGuidedTour, withoutGuidedTourIntent, isCreateIntent, CreateWorkflowRefusalDialog, decideMissingWorkflow } from './chunk-7XW6BC4B.js';
2
2
  import { registerLiveCanvasHandler } from './chunk-PIKYQE7P.js';
3
- import { useWorkflowReloadStore, releaseLeaseKeptForReload, useInstalledNodes, buildNodeCheck, FlowBuilderPage, buildRequiredNodeTypes } from './chunk-66AKFXP7.js';
3
+ import { useWorkflowReloadStore, releaseLeaseKeptForReload, useInstalledNodes, buildNodeCheck, FlowBuilderPage, buildRequiredNodeTypes } from './chunk-MJ5EI5OT.js';
4
4
  import { canvasFromDefinition } from './chunk-2CXUTQUH.js';
5
5
  import { useLazyWorkflowDefinition, useCreateWorkflow } from './chunk-KJB3WZVD.js';
6
6
  import { refetchStoreNodes, BILLING_PLANS_PATH, codeBuildIdOf, rememberRevision, blankWorkflowCreateInput, classifyCreateRefusal, useSaveStatusStore, workflowFingerprint, registerWorkflowNodeTypes } from './chunk-GI6BQIQA.js';
@@ -2955,6 +2955,393 @@ function PlatformEventSourceSelect({
2955
2955
  notice && /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: notice })
2956
2956
  ] });
2957
2957
  }
2958
+ var TIMECAMPUS_TIME_TRIGGER_FIELD = "timecampus-time-trigger";
2959
+ var MAX_TIME_TRIGGER_IDS = 20;
2960
+ var MY_TIME_TRIGGERS_QUERY = gql`
2961
+ query FluidGridsTimeCampusMyTimeTriggers {
2962
+ timeCampusMyTimeTriggers {
2963
+ id
2964
+ name
2965
+ metric
2966
+ comparator
2967
+ threshold
2968
+ enabled
2969
+ }
2970
+ }
2971
+ `;
2972
+ var CACHE_TTL_MS3 = 30 * 1e3;
2973
+ var caches2 = /* @__PURE__ */ new WeakMap();
2974
+ function isRecord3(value) {
2975
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2976
+ }
2977
+ function readTimeTriggers(data) {
2978
+ const list = isRecord3(data) ? data.timeCampusMyTimeTriggers : void 0;
2979
+ if (!Array.isArray(list)) return [];
2980
+ const triggers = [];
2981
+ for (const item of list) {
2982
+ if (!isRecord3(item) || typeof item.id !== "string" || !item.id) continue;
2983
+ triggers.push({
2984
+ id: item.id,
2985
+ name: typeof item.name === "string" && item.name.trim() ? item.name.trim() : item.id,
2986
+ metric: typeof item.metric === "string" ? item.metric : "",
2987
+ comparator: typeof item.comparator === "string" ? item.comparator : "",
2988
+ threshold: typeof item.threshold === "number" && Number.isFinite(item.threshold) ? item.threshold : null,
2989
+ enabled: item.enabled !== false
2990
+ });
2991
+ }
2992
+ return triggers.sort(
2993
+ (a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" })
2994
+ );
2995
+ }
2996
+ function fetchTimeTriggers(getClient = getFluidGridsClient, now = Date.now) {
2997
+ const client = getClient();
2998
+ const cached = caches2.get(client);
2999
+ if (cached && cached.expiresAt > now()) return cached.triggers;
3000
+ const triggers = client.query({ query: MY_TIME_TRIGGERS_QUERY, fetchPolicy: "network-only" }).then((result) => readTimeTriggers(result.data));
3001
+ const entry = { triggers, expiresAt: now() + CACHE_TTL_MS3 };
3002
+ caches2.set(client, entry);
3003
+ triggers.catch(() => {
3004
+ if (caches2.get(client) === entry) caches2.delete(client);
3005
+ });
3006
+ return triggers;
3007
+ }
3008
+ function parseTriggerIds(value) {
3009
+ const items = Array.isArray(value) ? value.filter((item) => typeof item === "string") : typeof value === "string" ? value.split(",") : [];
3010
+ return [...new Set(items.map((item) => item.trim()).filter(Boolean))];
3011
+ }
3012
+ function formatTriggerIds(ids) {
3013
+ return parseTriggerIds([...ids]).join(",");
3014
+ }
3015
+ function toggleTriggerId(ids, id) {
3016
+ return ids.includes(id) ? ids.filter((existing) => existing !== id) : [...ids, id];
3017
+ }
3018
+ function isTypedTriggerId(text) {
3019
+ return /^[A-Za-z0-9][A-Za-z0-9_:.-]{0,127}$/.test(text.trim());
3020
+ }
3021
+ function filterTimeTriggers(triggers, query) {
3022
+ const needle = query.trim().toLowerCase();
3023
+ if (!needle) return [...triggers];
3024
+ return triggers.filter(
3025
+ (trigger) => trigger.name.toLowerCase().includes(needle) || trigger.id.toLowerCase().includes(needle)
3026
+ );
3027
+ }
3028
+ var METRIC_TEXT = {
3029
+ TRACKED_HOURS: ["trackedHours", "Tracked hours"],
3030
+ FOCUS_PERCENT: ["focusPercent", "Focus %"],
3031
+ CONTEXT_SWITCHES: ["contextSwitches", "Context switches"],
3032
+ LONGEST_FOCUS_MINUTES: ["longestFocus", "Longest focus (min)"]
3033
+ };
3034
+ var KEY = "fluidgrids.builder.timeCampusTrigger";
3035
+ function describeTimeTrigger(trigger, tr = (_key, fallback) => fallback) {
3036
+ const metricText = METRIC_TEXT[trigger.metric];
3037
+ const metric = metricText ? tr(`${KEY}.metric.${metricText[0]}`, metricText[1]) : trigger.metric;
3038
+ const comparator = trigger.comparator === "BELOW" ? tr(`${KEY}.below`, "below") : trigger.comparator === "ABOVE" ? tr(`${KEY}.above`, "above") : trigger.comparator.toLowerCase();
3039
+ return [metric, comparator, trigger.threshold ?? ""].filter((part) => part !== "").join(" ");
3040
+ }
3041
+ var KEY2 = "fluidgrids.builder.timeCampusTrigger";
3042
+ function useTimeTriggers() {
3043
+ const [state, setState] = useState({ status: "loading" });
3044
+ useEffect(() => {
3045
+ let cancelled = false;
3046
+ fetchTimeTriggers().then((triggers) => {
3047
+ if (!cancelled) setState({ status: "ready", triggers });
3048
+ }).catch(() => {
3049
+ if (!cancelled) setState({ status: "failed" });
3050
+ });
3051
+ return () => {
3052
+ cancelled = true;
3053
+ };
3054
+ }, []);
3055
+ return state;
3056
+ }
3057
+ function useTriggerPicker(value, state, query) {
3058
+ const selected = useMemo(() => parseTriggerIds(value), [value]);
3059
+ const selectedSet = useMemo(() => new Set(selected), [selected]);
3060
+ const triggers = useMemo(
3061
+ () => state.status === "ready" ? state.triggers : [],
3062
+ [state]
3063
+ );
3064
+ const byId = useMemo(
3065
+ () => new Map(triggers.map((trigger) => [trigger.id, trigger])),
3066
+ [triggers]
3067
+ );
3068
+ const matches = useMemo(
3069
+ () => filterTimeTriggers(triggers, query),
3070
+ [triggers, query]
3071
+ );
3072
+ const unlisted = selected.filter((id) => !byId.has(id));
3073
+ const typed = query.trim();
3074
+ const full = selected.length >= MAX_TIME_TRIGGER_IDS;
3075
+ const canUseTyped = typed !== "" && isTypedTriggerId(typed) && !byId.has(typed) && !selectedSet.has(typed) && !full;
3076
+ const showNoMatch = state.status === "ready" && triggers.length > 0 && matches.length === 0 && !canUseTyped;
3077
+ return {
3078
+ selected,
3079
+ selectedSet,
3080
+ byId,
3081
+ matches,
3082
+ unlisted,
3083
+ typed,
3084
+ full,
3085
+ canUseTyped,
3086
+ showNoMatch
3087
+ };
3088
+ }
3089
+ function pickerNotice(state, full, tr) {
3090
+ if (state.status === "failed") {
3091
+ return tr(
3092
+ `${KEY2}.loadFailed`,
3093
+ "Your time triggers could not be loaded. Type a trigger id instead."
3094
+ );
3095
+ }
3096
+ if (state.status === "ready" && state.triggers.length === 0) {
3097
+ return tr(
3098
+ `${KEY2}.none`,
3099
+ "You have no time triggers yet. Create one in TimeCampus > Triggers, or type an id."
3100
+ );
3101
+ }
3102
+ if (full) {
3103
+ return tr(`${KEY2}.full`, "At most {{max}} triggers can be picked.", {
3104
+ max: MAX_TIME_TRIGGER_IDS
3105
+ });
3106
+ }
3107
+ return null;
3108
+ }
3109
+ var ROW_CX = "flex w-full cursor-pointer items-center gap-2 px-3 py-1.5 text-left text-sm hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50";
3110
+ function PickRow({
3111
+ picked,
3112
+ onClick,
3113
+ disabled,
3114
+ testId,
3115
+ value,
3116
+ className,
3117
+ children
3118
+ }) {
3119
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
3120
+ "button",
3121
+ {
3122
+ type: "button",
3123
+ "aria-pressed": picked,
3124
+ onClick,
3125
+ disabled,
3126
+ "data-testid": testId,
3127
+ "data-value": value,
3128
+ className: cn(ROW_CX, picked && "bg-muted/60", className),
3129
+ children: [
3130
+ /* @__PURE__ */ jsx(
3131
+ Icon,
3132
+ {
3133
+ icon: picked ? "mdi:checkbox-marked" : "mdi:checkbox-blank-outline",
3134
+ className: "size-4 shrink-0 text-muted-foreground"
3135
+ }
3136
+ ),
3137
+ children
3138
+ ]
3139
+ }
3140
+ ) });
3141
+ }
3142
+ function ListMessage({ children }) {
3143
+ return /* @__PURE__ */ jsx("li", { className: "py-3 text-center text-xs text-muted-foreground", children });
3144
+ }
3145
+ function TimeCampusTriggerSelect({
3146
+ value,
3147
+ onChange,
3148
+ className,
3149
+ testId,
3150
+ label,
3151
+ initialState
3152
+ }) {
3153
+ const tr = useTr();
3154
+ const fetched = useTimeTriggers();
3155
+ const state = initialState ?? fetched;
3156
+ const [open, setOpen] = useState(false);
3157
+ const [query, setQuery] = useState("");
3158
+ const listId = useId();
3159
+ const {
3160
+ selected,
3161
+ selectedSet,
3162
+ byId,
3163
+ matches,
3164
+ unlisted,
3165
+ typed,
3166
+ full,
3167
+ canUseTyped,
3168
+ showNoMatch
3169
+ } = useTriggerPicker(value, state, query);
3170
+ const fieldLabel = label ?? tr(`${KEY2}.label`, "Time Triggers");
3171
+ const anyText = tr(`${KEY2}.any`, "Any of my triggers");
3172
+ const searchText = tr(`${KEY2}.search`, "Search your triggers or type an id\u2026");
3173
+ const summary = selected.length === 0 ? anyText : selected.map((id) => byId.get(id)?.name ?? id).join(", ");
3174
+ const notice = pickerNotice(state, full, tr);
3175
+ const rowTestId = (suffix) => testId ? `${testId}-${suffix}` : void 0;
3176
+ const write = (ids) => onChange(formatTriggerIds(ids));
3177
+ const toggle = (id) => {
3178
+ if (!selectedSet.has(id) && full) return;
3179
+ write(toggleTriggerId(selected, id));
3180
+ };
3181
+ const addTyped = () => {
3182
+ if (!canUseTyped) return;
3183
+ write([...selected, typed]);
3184
+ setQuery("");
3185
+ };
3186
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
3187
+ /* @__PURE__ */ jsxs(
3188
+ Popover,
3189
+ {
3190
+ open,
3191
+ onOpenChange: (next) => {
3192
+ setOpen(next);
3193
+ if (!next) setQuery("");
3194
+ },
3195
+ children: [
3196
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
3197
+ "button",
3198
+ {
3199
+ type: "button",
3200
+ role: "combobox",
3201
+ "aria-expanded": open,
3202
+ "aria-haspopup": "dialog",
3203
+ "aria-controls": listId,
3204
+ "aria-label": fieldLabel,
3205
+ "data-testid": testId,
3206
+ className: cn(
3207
+ className,
3208
+ "flex items-center justify-between gap-2 text-left"
3209
+ ),
3210
+ children: [
3211
+ /* @__PURE__ */ jsx(
3212
+ "span",
3213
+ {
3214
+ className: cn(
3215
+ "truncate",
3216
+ selected.length === 0 && "text-muted-foreground"
3217
+ ),
3218
+ children: summary
3219
+ }
3220
+ ),
3221
+ /* @__PURE__ */ jsx(
3222
+ Icon,
3223
+ {
3224
+ icon: "mdi:unfold-more-horizontal",
3225
+ className: "size-4 shrink-0 text-muted-foreground"
3226
+ }
3227
+ )
3228
+ ]
3229
+ }
3230
+ ) }),
3231
+ /* @__PURE__ */ jsxs(
3232
+ PopoverContent,
3233
+ {
3234
+ id: listId,
3235
+ align: "start",
3236
+ "aria-label": fieldLabel,
3237
+ className: "w-[var(--radix-popover-trigger-width)] min-w-64 p-0",
3238
+ style: { zIndex: NDV_OVERLAY_Z_INDEX },
3239
+ children: [
3240
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3", children: [
3241
+ /* @__PURE__ */ jsx(
3242
+ Icon,
3243
+ {
3244
+ icon: "mdi:magnify",
3245
+ className: "size-4 shrink-0 text-muted-foreground"
3246
+ }
3247
+ ),
3248
+ /* @__PURE__ */ jsx(
3249
+ "input",
3250
+ {
3251
+ type: "text",
3252
+ value: query,
3253
+ onChange: (event) => setQuery(event.target.value),
3254
+ onKeyDown: (event) => {
3255
+ if (event.key !== "Enter") return;
3256
+ event.preventDefault();
3257
+ if (matches[0]) toggle(matches[0].id);
3258
+ else addTyped();
3259
+ },
3260
+ placeholder: searchText,
3261
+ "aria-label": searchText,
3262
+ "data-testid": rowTestId("search"),
3263
+ className: "h-9 flex-1 border-0 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
3264
+ }
3265
+ )
3266
+ ] }),
3267
+ /* @__PURE__ */ jsxs("ul", { className: "max-h-64 overflow-y-auto py-1", children: [
3268
+ /* @__PURE__ */ jsx(
3269
+ PickRow,
3270
+ {
3271
+ picked: selected.length === 0,
3272
+ onClick: () => {
3273
+ write([]);
3274
+ setOpen(false);
3275
+ },
3276
+ testId: rowTestId("any"),
3277
+ className: "italic text-muted-foreground",
3278
+ children: anyText
3279
+ }
3280
+ ),
3281
+ state.status === "loading" && /* @__PURE__ */ jsx(ListMessage, { children: tr(`${KEY2}.loading`, "Loading\u2026") }),
3282
+ showNoMatch && /* @__PURE__ */ jsx(ListMessage, { children: tr(
3283
+ `${KEY2}.empty`,
3284
+ "No trigger matches. Type the full id to use it."
3285
+ ) }),
3286
+ matches.map((trigger) => {
3287
+ const picked = selectedSet.has(trigger.id);
3288
+ return /* @__PURE__ */ jsx(
3289
+ PickRow,
3290
+ {
3291
+ picked,
3292
+ onClick: () => toggle(trigger.id),
3293
+ disabled: !picked && full,
3294
+ testId: rowTestId("option"),
3295
+ value: trigger.id,
3296
+ children: /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
3297
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: trigger.name }),
3298
+ /* @__PURE__ */ jsxs("span", { className: "truncate text-[11px] text-muted-foreground", children: [
3299
+ describeTimeTrigger(trigger, tr),
3300
+ !trigger.enabled && ` \xB7 ${tr(`${KEY2}.paused`, "paused")}`
3301
+ ] })
3302
+ ] })
3303
+ },
3304
+ trigger.id
3305
+ );
3306
+ }),
3307
+ unlisted.map((id) => /* @__PURE__ */ jsx(
3308
+ PickRow,
3309
+ {
3310
+ picked: true,
3311
+ onClick: () => toggle(id),
3312
+ testId: rowTestId("unlisted"),
3313
+ value: id,
3314
+ children: /* @__PURE__ */ jsx("span", { className: "truncate font-mono text-xs", children: id })
3315
+ },
3316
+ id
3317
+ )),
3318
+ canUseTyped && /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
3319
+ "button",
3320
+ {
3321
+ type: "button",
3322
+ onClick: addTyped,
3323
+ "data-testid": rowTestId("typed"),
3324
+ className: ROW_CX,
3325
+ children: [
3326
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:keyboard-outline", className: "size-3.5" }),
3327
+ /* @__PURE__ */ jsxs("span", { children: [
3328
+ tr(`${KEY2}.useTyped`, "Use id"),
3329
+ " ",
3330
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: typed })
3331
+ ] })
3332
+ ]
3333
+ }
3334
+ ) })
3335
+ ] })
3336
+ ]
3337
+ }
3338
+ )
3339
+ ]
3340
+ }
3341
+ ),
3342
+ notice && /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: notice })
3343
+ ] });
3344
+ }
2958
3345
  var SECTION_LABEL3 = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
2959
3346
  function NodeParamsForm({
2960
3347
  config,
@@ -3236,7 +3623,7 @@ function NodeParamsForm({
3236
3623
  const isCodeParam = p.type === "code" || nodeType === "internal:code" && p.name === "code";
3237
3624
  const highlight = dragField === p.name;
3238
3625
  const controlId = `${formId}-param-${p.name}`;
3239
- const nativeControl = mode !== "expression" && !isCodeParam && !(structuredType && !optionList) && !p.loadOptions && p.type !== PLATFORM_EVENT_MODULE_FIELD && p.type !== PLATFORM_EVENT_RECORD_TYPE_FIELD;
3626
+ const nativeControl = mode !== "expression" && !isCodeParam && !(structuredType && !optionList) && !p.loadOptions && p.type !== PLATFORM_EVENT_MODULE_FIELD && p.type !== PLATFORM_EVENT_RECORD_TYPE_FIELD && p.type !== TIMECAMPUS_TIME_TRIGGER_FIELD;
3240
3627
  const inputCx = cn(
3241
3628
  "w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none transition-colors disabled:opacity-50",
3242
3629
  highlight ? "border-primary ring-1 ring-primary" : "border-input focus:border-primary"
@@ -3446,6 +3833,15 @@ function NodeParamsForm({
3446
3833
  className: inputCx,
3447
3834
  testId: `${testIdPrefix}-param-${p.name}`
3448
3835
  }
3836
+ ) : p.type === TIMECAMPUS_TIME_TRIGGER_FIELD ? /* @__PURE__ */ jsx(
3837
+ TimeCampusTriggerSelect,
3838
+ {
3839
+ value: fixedValue,
3840
+ label: p.label,
3841
+ onChange: (next) => writeFixed(p.name, next || void 0),
3842
+ className: inputCx,
3843
+ testId: `${testIdPrefix}-param-${p.name}`
3844
+ }
3449
3845
  ) : p.type === "timezone" ? /* @__PURE__ */ jsx(
3450
3846
  TimezoneSelect,
3451
3847
  {
@@ -21244,12 +21244,12 @@ function EditLeaseBanner() {
21244
21244
  );
21245
21245
  }
21246
21246
  var LazyNodeDetailView = lazy(
21247
- () => import('./node-detail-view-BGWKE6KW.js').then((m) => ({
21247
+ () => import('./node-detail-view-PLCZIWJS.js').then((m) => ({
21248
21248
  default: m.NodeDetailView
21249
21249
  }))
21250
21250
  );
21251
21251
  var LazyQuickConfigPanel = lazy(
21252
- () => import('./quick-config-panel-67BU4I23.js').then((m) => ({
21252
+ () => import('./quick-config-panel-FMDKFGSY.js').then((m) => ({
21253
21253
  default: m.QuickConfigPanel
21254
21254
  }))
21255
21255
  );
@@ -1,4 +1,4 @@
1
- import { useNodeSelectedEvent, Card, FluidGridsMark } from './chunk-66AKFXP7.js';
1
+ import { useNodeSelectedEvent, Card, FluidGridsMark } from './chunk-MJ5EI5OT.js';
2
2
  import { Input } from './chunk-IEVSFCFS.js';
3
3
  import { useWorkflowDefinition } from './chunk-KJB3WZVD.js';
4
4
  import { Button } from './chunk-QJAZXCFQ.js';
@@ -1,4 +1,4 @@
1
- import { useImportWorkflowTemplate, FlowBuilderPage, useWorkflowTemplates, TEMPLATE_STORE_CATEGORIES, TemplateCard, useTemplateSubmissions, usePublishToStore } from './chunk-66AKFXP7.js';
1
+ import { useImportWorkflowTemplate, FlowBuilderPage, useWorkflowTemplates, TEMPLATE_STORE_CATEGORIES, TemplateCard, useTemplateSubmissions, usePublishToStore } from './chunk-MJ5EI5OT.js';
2
2
  import { QuotaExhaustedDialog } from './chunk-JSJSBB76.js';
3
3
  import { templatesControls } from './chunk-FNZZ5JJL.js';
4
4
  import { useWorkflowDefinitions, useDuplicateWorkflow, useLazyWorkflowDefinition } from './chunk-KJB3WZVD.js';
@@ -1,7 +1,7 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as React from 'react';
2
2
 
3
- declare function ExecutionsListPage(): react_jsx_runtime.JSX.Element;
3
+ declare function ExecutionsListPage(): React.JSX.Element;
4
4
 
5
- declare function ExecutionDetailPage(): react_jsx_runtime.JSX.Element;
5
+ declare function ExecutionDetailPage(): React.JSX.Element;
6
6
 
7
7
  export { ExecutionDetailPage, ExecutionsListPage };
package/dist/history.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as React from 'react';
2
2
 
3
- declare function HistoryPage(): react_jsx_runtime.JSX.Element;
3
+ declare function HistoryPage(): React.JSX.Element;
4
4
 
5
5
  export { HistoryPage };