nucleus-core-ts 0.9.798 → 0.9.800
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/dist/fe/components/IntegrationsPage/components/ConnectionList.js +11 -9
- package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.d.ts +11 -3
- package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.js +93 -59
- package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.d.ts +1 -1
- package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.js +143 -109
- package/dist/fe/components/IntegrationsPage/components/RunHistory.js +3 -1
- package/dist/fe/components/IntegrationsPage/components/RunPanel.js +4 -2
- package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.js +6 -4
- package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.js +8 -6
- package/dist/fe/components/IntegrationsPage/components/RunsTab.js +3 -1
- package/dist/fe/components/IntegrationsPage/index.d.ts +2 -0
- package/dist/fe/components/IntegrationsPage/index.js +1 -0
- package/dist/fe/components/IntegrationsPage/store/index.js +6 -0
- package/dist/fe/components/IntegrationsPage/text/context.d.ts +7 -0
- package/dist/fe/components/IntegrationsPage/text/context.js +20 -0
- package/dist/fe/components/IntegrationsPage/text/index.d.ts +117 -0
- package/dist/fe/components/IntegrationsPage/text/index.js +127 -0
- package/dist/fe/components/IntegrationsPage/text/text.test.d.ts +1 -0
- package/dist/fe/components/IntegrationsPage/text/text.test.js +67 -0
- package/dist/fe/components/IntegrationsPage/theme/index.d.ts +16 -0
- package/dist/fe/components/IntegrationsPage/theme/index.js +18 -0
- package/dist/fe/components/IntegrationsPage/types/actions.d.ts +9 -0
- package/dist/fe/index.d.ts +2 -1
- package/dist/fe/index.js +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useEffect, useEffectEvent, useState } from 'react';
|
|
4
4
|
import { cn } from '../../../utils/cn';
|
|
5
5
|
import { useIntegrationsStore } from '../store';
|
|
6
|
+
import { useLabels } from '../text/context';
|
|
6
7
|
import { integrationsPageTheme } from '../theme';
|
|
7
8
|
import { Badge, Field, Notice } from './Primitives';
|
|
8
9
|
import { ListSkeleton } from './Skeletons';
|
|
@@ -19,6 +20,7 @@ const PAGE_SIZE = 20;
|
|
|
19
20
|
const SEARCH_DEBOUNCE_MS = 300;
|
|
20
21
|
export function ConnectionList({ actions, onNewConnection, reloadToken = 0 }) {
|
|
21
22
|
const store = useIntegrationsStore();
|
|
23
|
+
const labels = useLabels();
|
|
22
24
|
const search = store.sourcesSearch;
|
|
23
25
|
const selectedId = store.selectedSourceId;
|
|
24
26
|
const [term, setTerm] = useState(search);
|
|
@@ -84,11 +86,11 @@ export function ConnectionList({ actions, onNewConnection, reloadToken = 0 }) {
|
|
|
84
86
|
children: [
|
|
85
87
|
/*#__PURE__*/ _jsx("h2", {
|
|
86
88
|
className: theme.header.title,
|
|
87
|
-
children:
|
|
89
|
+
children: labels.connections.heading
|
|
88
90
|
}),
|
|
89
91
|
/*#__PURE__*/ _jsx("p", {
|
|
90
92
|
className: theme.header.subtitle,
|
|
91
|
-
children: list.total === 1 ?
|
|
93
|
+
children: list.total === 1 ? labels.connections.countOne : labels.connections.countMany(list.total)
|
|
92
94
|
})
|
|
93
95
|
]
|
|
94
96
|
}),
|
|
@@ -98,19 +100,19 @@ export function ConnectionList({ actions, onNewConnection, reloadToken = 0 }) {
|
|
|
98
100
|
className: theme.button.primary,
|
|
99
101
|
onClick: handleNew,
|
|
100
102
|
type: "button",
|
|
101
|
-
children:
|
|
103
|
+
children: labels.connections.create
|
|
102
104
|
})
|
|
103
105
|
})
|
|
104
106
|
]
|
|
105
107
|
}),
|
|
106
108
|
/*#__PURE__*/ _jsx(Field, {
|
|
107
109
|
htmlFor: "connection-search",
|
|
108
|
-
label:
|
|
110
|
+
label: labels.connections.searchLabel,
|
|
109
111
|
children: /*#__PURE__*/ _jsx("input", {
|
|
110
112
|
className: theme.field.input,
|
|
111
113
|
id: "connection-search",
|
|
112
114
|
onChange: (event)=>setTerm(event.target.value),
|
|
113
|
-
placeholder:
|
|
115
|
+
placeholder: labels.connections.searchPlaceholder,
|
|
114
116
|
type: "search",
|
|
115
117
|
value: term
|
|
116
118
|
})
|
|
@@ -119,7 +121,7 @@ export function ConnectionList({ actions, onNewConnection, reloadToken = 0 }) {
|
|
|
119
121
|
rows: 6
|
|
120
122
|
}) : null,
|
|
121
123
|
list.error ? /*#__PURE__*/ _jsxs(Notice, {
|
|
122
|
-
title:
|
|
124
|
+
title: labels.connections.loadFailed,
|
|
123
125
|
tone: "danger",
|
|
124
126
|
children: [
|
|
125
127
|
list.error,
|
|
@@ -128,13 +130,13 @@ export function ConnectionList({ actions, onNewConnection, reloadToken = 0 }) {
|
|
|
128
130
|
className: theme.button.ghost,
|
|
129
131
|
onClick: list.reload,
|
|
130
132
|
type: "button",
|
|
131
|
-
children:
|
|
133
|
+
children: labels.connections.retry
|
|
132
134
|
})
|
|
133
135
|
]
|
|
134
136
|
}) : null,
|
|
135
137
|
!firstLoad && list.items.length === 0 && !list.error ? /*#__PURE__*/ _jsx("p", {
|
|
136
138
|
className: theme.list.empty,
|
|
137
|
-
children: search.length > 0 ?
|
|
139
|
+
children: search.length > 0 ? labels.connections.emptySearch(search) : labels.connections.empty
|
|
138
140
|
}) : null,
|
|
139
141
|
list.items.length > 0 ? /*#__PURE__*/ _jsx("ul", {
|
|
140
142
|
className: theme.list.wrapper,
|
|
@@ -154,7 +156,7 @@ export function ConnectionList({ actions, onNewConnection, reloadToken = 0 }) {
|
|
|
154
156
|
}),
|
|
155
157
|
source.enabled === false ? /*#__PURE__*/ _jsx(Badge, {
|
|
156
158
|
tone: "neutral",
|
|
157
|
-
children:
|
|
159
|
+
children: labels.connections.off
|
|
158
160
|
}) : null
|
|
159
161
|
]
|
|
160
162
|
}),
|
|
@@ -2,11 +2,19 @@ import type { ConnectionTab } from '../store/state';
|
|
|
2
2
|
/** Shared with the panel so `aria-controls` and `aria-labelledby` line up. */
|
|
3
3
|
export declare function connectionTabId(tab: ConnectionTab): string;
|
|
4
4
|
export declare function connectionTabPanelId(tab: ConnectionTab): string;
|
|
5
|
+
/** What a step has to show about itself beyond its name. */
|
|
6
|
+
export type ConnectionStepState = {
|
|
7
|
+
/** Shown in place of the number, and in the step's own words. */
|
|
8
|
+
done?: boolean;
|
|
9
|
+
/** The step before it is unfinished; still reachable, but said so. */
|
|
10
|
+
locked?: boolean;
|
|
11
|
+
/** One short phrase proving the step is done — "3 calls, all sampled". */
|
|
12
|
+
evidence?: string;
|
|
13
|
+
};
|
|
5
14
|
export type ConnectionTabsProps = {
|
|
6
15
|
value: ConnectionTab;
|
|
7
16
|
onChange: (tab: ConnectionTab) => void;
|
|
8
|
-
|
|
9
|
-
counts?: Partial<Record<ConnectionTab, number>>;
|
|
17
|
+
steps?: Partial<Record<ConnectionTab, ConnectionStepState>>;
|
|
10
18
|
label?: string;
|
|
11
19
|
};
|
|
12
|
-
export declare function ConnectionTabs({ value, onChange,
|
|
20
|
+
export declare function ConnectionTabs({ value, onChange, steps, label, }: ConnectionTabsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,32 +2,29 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useRef } from 'react';
|
|
4
4
|
import { cn } from '../../../utils/cn';
|
|
5
|
+
import { useLabels } from '../text/context';
|
|
5
6
|
import { integrationsPageTheme } from '../theme';
|
|
6
7
|
/**
|
|
7
|
-
* The
|
|
8
|
-
* its mappings and its runs.
|
|
8
|
+
* The four phases of an import, in the order they happen.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* These were four flat tabs — Connection, Endpoints, Mappings, Runs — sitting
|
|
11
|
+
* side by side with nothing to say that they are a SEQUENCE, that a mapping is
|
|
12
|
+
* meaningless before a call has been tried, or which of them the operator had
|
|
13
|
+
* already finished. People opened the middle one first and met a dead end.
|
|
14
|
+
*
|
|
15
|
+
* So each is a step: a number, its name, one sentence saying what it is for, and
|
|
16
|
+
* a line of evidence once it is done. What is on screen now answers "where am I,
|
|
17
|
+
* what is this for, and what have I already got".
|
|
18
|
+
*
|
|
19
|
+
* It remains a real ARIA tablist — one stop in the tab order, arrow keys between
|
|
20
|
+
* the steps. A row of separately tabbable cards would make a keyboard user press
|
|
21
|
+
* Tab four times to leave a strip they were never inside.
|
|
13
22
|
*/ const theme = integrationsPageTheme;
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
value: 'endpoints',
|
|
21
|
-
label: 'Endpoints'
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
value: 'mappings',
|
|
25
|
-
label: 'Mappings'
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
value: 'runs',
|
|
29
|
-
label: 'Runs'
|
|
30
|
-
}
|
|
23
|
+
const ORDER = [
|
|
24
|
+
'connection',
|
|
25
|
+
'endpoints',
|
|
26
|
+
'mappings',
|
|
27
|
+
'runs'
|
|
31
28
|
];
|
|
32
29
|
/** Shared with the panel so `aria-controls` and `aria-labelledby` line up. */ export function connectionTabId(tab) {
|
|
33
30
|
return `integrations-tab-${tab}`;
|
|
@@ -35,18 +32,25 @@ const TABS = [
|
|
|
35
32
|
export function connectionTabPanelId(tab) {
|
|
36
33
|
return `integrations-panel-${tab}`;
|
|
37
34
|
}
|
|
38
|
-
export function ConnectionTabs({ value, onChange,
|
|
35
|
+
export function ConnectionTabs({ value, onChange, steps, label = 'Import steps' }) {
|
|
36
|
+
const labels = useLabels();
|
|
39
37
|
const buttonsRef = useRef({});
|
|
38
|
+
const copy = {
|
|
39
|
+
connection: labels.journey.connection,
|
|
40
|
+
endpoints: labels.journey.endpoints,
|
|
41
|
+
mappings: labels.journey.mappings,
|
|
42
|
+
runs: labels.journey.runs
|
|
43
|
+
};
|
|
40
44
|
const select = (index)=>{
|
|
41
|
-
const next =
|
|
45
|
+
const next = ORDER[(index % ORDER.length + ORDER.length) % ORDER.length];
|
|
42
46
|
if (!next) return;
|
|
43
|
-
onChange(next
|
|
47
|
+
onChange(next);
|
|
44
48
|
// Selection follows focus, so the focus has to follow the selection too —
|
|
45
|
-
// otherwise the next arrow press starts from the
|
|
46
|
-
buttonsRef.current[next
|
|
49
|
+
// otherwise the next arrow press starts from the step the user just left.
|
|
50
|
+
buttonsRef.current[next]?.focus();
|
|
47
51
|
};
|
|
48
52
|
const handleKeyDown = (event, tab)=>{
|
|
49
|
-
const index =
|
|
53
|
+
const index = ORDER.indexOf(tab);
|
|
50
54
|
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
|
|
51
55
|
event.preventDefault();
|
|
52
56
|
select(index + 1);
|
|
@@ -58,38 +62,68 @@ export function ConnectionTabs({ value, onChange, counts, label = 'Connection se
|
|
|
58
62
|
select(0);
|
|
59
63
|
} else if (event.key === 'End') {
|
|
60
64
|
event.preventDefault();
|
|
61
|
-
select(
|
|
65
|
+
select(ORDER.length - 1);
|
|
62
66
|
}
|
|
63
67
|
};
|
|
64
|
-
return /*#__PURE__*/
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"aria-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
68
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
69
|
+
className: "flex flex-col gap-2.5",
|
|
70
|
+
children: [
|
|
71
|
+
/*#__PURE__*/ _jsx("p", {
|
|
72
|
+
className: theme.steps.intro,
|
|
73
|
+
children: labels.journey.intro
|
|
74
|
+
}),
|
|
75
|
+
/*#__PURE__*/ _jsx("div", {
|
|
76
|
+
"aria-label": label,
|
|
77
|
+
className: theme.steps.wrapper,
|
|
78
|
+
role: "tablist",
|
|
79
|
+
children: ORDER.map((tab, index)=>{
|
|
80
|
+
const selected = tab === value;
|
|
81
|
+
const state = steps?.[tab] ?? {};
|
|
82
|
+
const { name, purpose } = copy[tab];
|
|
83
|
+
return /*#__PURE__*/ _jsxs("button", {
|
|
84
|
+
"aria-controls": connectionTabPanelId(tab),
|
|
85
|
+
"aria-selected": selected,
|
|
86
|
+
className: cn(theme.steps.item, state.done && !selected && theme.steps.itemDone, state.locked && !selected && theme.steps.itemLocked, selected && theme.steps.itemActive),
|
|
87
|
+
id: connectionTabId(tab),
|
|
88
|
+
onClick: ()=>onChange(tab),
|
|
89
|
+
onKeyDown: (event)=>handleKeyDown(event, tab),
|
|
90
|
+
ref: (node)=>{
|
|
91
|
+
buttonsRef.current[tab] = node;
|
|
92
|
+
},
|
|
93
|
+
role: "tab",
|
|
94
|
+
// Roving tabindex: the whole strip is ONE stop in the tab order.
|
|
95
|
+
tabIndex: selected ? 0 : -1,
|
|
96
|
+
type: "button",
|
|
97
|
+
children: [
|
|
98
|
+
/*#__PURE__*/ _jsx("span", {
|
|
99
|
+
"aria-hidden": "true",
|
|
100
|
+
className: cn(theme.steps.marker, selected ? theme.steps.markerActive : state.done ? theme.steps.markerDone : state.locked ? theme.steps.markerLocked : theme.steps.markerLocked),
|
|
101
|
+
children: state.done && !selected ? '✓' : index + 1
|
|
102
|
+
}),
|
|
103
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
104
|
+
className: "min-w-0 flex-1",
|
|
105
|
+
children: [
|
|
106
|
+
/*#__PURE__*/ _jsx("span", {
|
|
107
|
+
className: cn(theme.steps.name, 'block'),
|
|
108
|
+
children: name
|
|
109
|
+
}),
|
|
110
|
+
/*#__PURE__*/ _jsx("span", {
|
|
111
|
+
className: cn(theme.steps.purpose, 'block'),
|
|
112
|
+
children: purpose
|
|
113
|
+
}),
|
|
114
|
+
state.evidence ? /*#__PURE__*/ _jsx("span", {
|
|
115
|
+
className: cn(theme.steps.evidence, 'block'),
|
|
116
|
+
children: state.evidence
|
|
117
|
+
}) : state.locked ? /*#__PURE__*/ _jsx("span", {
|
|
118
|
+
className: cn(theme.field.hint, 'mt-1 block'),
|
|
119
|
+
children: labels.journey.lockedHint
|
|
120
|
+
}) : null
|
|
121
|
+
]
|
|
122
|
+
})
|
|
123
|
+
]
|
|
124
|
+
}, tab);
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
]
|
|
94
128
|
});
|
|
95
129
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { IntegrationsPageProps } from '../types';
|
|
2
|
-
export declare function IntegrationsPage({ title, subtitle, className, actions, onSubscribeProgress, }: IntegrationsPageProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function IntegrationsPage({ title, subtitle, className, actions, labels: labelsOverride, onSubscribeProgress, }: IntegrationsPageProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -5,6 +5,8 @@ import gsap from 'gsap';
|
|
|
5
5
|
import { useEffect, useEffectEvent, useRef, useState } from 'react';
|
|
6
6
|
import { cn } from '../../../utils/cn';
|
|
7
7
|
import { useIntegrationsStore } from '../store';
|
|
8
|
+
import { resolveIntegrationsLabels } from '../text';
|
|
9
|
+
import { IntegrationsLabelsProvider } from '../text/context';
|
|
8
10
|
import { integrationsPageTheme } from '../theme';
|
|
9
11
|
import { ConnectionList } from './ConnectionList';
|
|
10
12
|
import { ConnectionSettingsTab } from './ConnectionSettingsTab';
|
|
@@ -22,8 +24,12 @@ import { RunsTab } from './RunsTab';
|
|
|
22
24
|
* page from the server rather than being handed a list to slice.
|
|
23
25
|
*/ gsap.registerPlugin(useGSAP);
|
|
24
26
|
const theme = integrationsPageTheme;
|
|
25
|
-
export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect an external service, describe the data you want and import it on your terms.', className, actions, onSubscribeProgress }) {
|
|
27
|
+
export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect an external service, describe the data you want and import it on your terms.', className, actions, labels: labelsOverride, onSubscribeProgress }) {
|
|
26
28
|
const store = useIntegrationsStore();
|
|
29
|
+
// Resolved here rather than read from the context, because this component is
|
|
30
|
+
// the one that PROVIDES it — and passed down so a control four levels deep
|
|
31
|
+
// does not need a `labels` prop of its own.
|
|
32
|
+
const labels = resolveIntegrationsLabels(labelsOverride);
|
|
27
33
|
const containerRef = useRef(null);
|
|
28
34
|
const contentRef = useRef(null);
|
|
29
35
|
/**
|
|
@@ -74,118 +80,146 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
74
80
|
};
|
|
75
81
|
const tab = store.tab;
|
|
76
82
|
const hasConnection = store.selectedSourceId !== null;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
83
|
+
/**
|
|
84
|
+
* What each step can say about itself.
|
|
85
|
+
*
|
|
86
|
+
* Evidence rather than a bare count: "3 calls, all tried" answers a question a
|
|
87
|
+
* number does not — whether the thing was only declared or actually run. A
|
|
88
|
+
* step whose prerequisite is missing is marked locked rather than hidden,
|
|
89
|
+
* because a step you cannot see is one you cannot understand the order of.
|
|
90
|
+
*/ const selectedSource = store.sources.find((s)=>s.id === store.selectedSourceId);
|
|
91
|
+
const sampled = store.endpoints.filter((e)=>(e.sampleResponse?.records?.length ?? 0) > 0).length;
|
|
92
|
+
const steps = {
|
|
93
|
+
connection: {
|
|
94
|
+
done: Boolean(selectedSource),
|
|
95
|
+
evidence: selectedSource ? labels.journey.evidence.connection(selectedSource.name) : undefined
|
|
96
|
+
},
|
|
97
|
+
endpoints: {
|
|
98
|
+
done: store.endpointsTotal > 0,
|
|
99
|
+
evidence: store.endpointsTotal > 0 ? labels.journey.evidence.endpoints(store.endpointsTotal, sampled) : undefined
|
|
100
|
+
},
|
|
101
|
+
mappings: {
|
|
102
|
+
done: store.mappingsTotal > 0,
|
|
103
|
+
locked: store.endpointsTotal === 0,
|
|
104
|
+
evidence: store.mappingsTotal > 0 ? labels.journey.evidence.mappings(store.mappingsTotal) : undefined
|
|
105
|
+
},
|
|
106
|
+
runs: {
|
|
107
|
+
done: store.runsTotal > 0,
|
|
108
|
+
locked: store.mappingsTotal === 0,
|
|
109
|
+
evidence: store.runsTotal > 0 ? labels.journey.evidence.runs(store.runsTotal) : undefined
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
return /*#__PURE__*/ _jsx(IntegrationsLabelsProvider, {
|
|
113
|
+
labels: labels,
|
|
114
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
115
|
+
className: cn(theme.container, className),
|
|
116
|
+
ref: containerRef,
|
|
117
|
+
children: [
|
|
118
|
+
/*#__PURE__*/ _jsx("header", {
|
|
119
|
+
className: theme.header.wrapper,
|
|
120
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
121
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
122
|
+
children: [
|
|
123
|
+
/*#__PURE__*/ _jsx("h1", {
|
|
124
|
+
className: theme.header.title,
|
|
125
|
+
children: title
|
|
126
|
+
}),
|
|
127
|
+
subtitle ? /*#__PURE__*/ _jsx("p", {
|
|
128
|
+
className: theme.header.subtitle,
|
|
129
|
+
children: subtitle
|
|
130
|
+
}) : null
|
|
131
|
+
]
|
|
132
|
+
})
|
|
133
|
+
}),
|
|
134
|
+
/*#__PURE__*/ _jsx("div", {
|
|
135
|
+
"aria-live": "assertive",
|
|
136
|
+
className: "contents",
|
|
137
|
+
role: "alert",
|
|
138
|
+
children: store.error ? /*#__PURE__*/ _jsxs("div", {
|
|
139
|
+
className: "relative",
|
|
140
|
+
children: [
|
|
141
|
+
/*#__PURE__*/ _jsx(Notice, {
|
|
142
|
+
title: labels.common.somethingWentWrong,
|
|
143
|
+
tone: "danger",
|
|
144
|
+
children: store.error
|
|
145
|
+
}),
|
|
146
|
+
/*#__PURE__*/ _jsx("button", {
|
|
147
|
+
"aria-label": labels.common.dismiss,
|
|
148
|
+
className: cn(theme.button.ghost, 'absolute top-1.5 right-1.5 h-8 w-8 px-0 sm:h-8'),
|
|
149
|
+
onClick: dismissError,
|
|
150
|
+
type: "button",
|
|
151
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
152
|
+
"aria-hidden": "true",
|
|
153
|
+
children: "✕"
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
]
|
|
157
|
+
}) : null
|
|
158
|
+
}),
|
|
159
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
160
|
+
className: theme.layout.split,
|
|
161
|
+
ref: contentRef,
|
|
103
162
|
children: [
|
|
104
|
-
/*#__PURE__*/ _jsx(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
children:
|
|
163
|
+
/*#__PURE__*/ _jsx("aside", {
|
|
164
|
+
"aria-label": labels.connections.heading,
|
|
165
|
+
className: theme.layout.aside,
|
|
166
|
+
children: /*#__PURE__*/ _jsx(ConnectionList, {
|
|
167
|
+
actions: actions,
|
|
168
|
+
reloadToken: sourcesReloadToken
|
|
169
|
+
})
|
|
108
170
|
}),
|
|
109
|
-
/*#__PURE__*/ _jsx("
|
|
110
|
-
"aria-label": "
|
|
111
|
-
className:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
171
|
+
/*#__PURE__*/ _jsx("section", {
|
|
172
|
+
"aria-label": "Connection detail",
|
|
173
|
+
className: theme.layout.main,
|
|
174
|
+
children: hasConnection ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
175
|
+
children: [
|
|
176
|
+
/*#__PURE__*/ _jsx(ConnectionTabs, {
|
|
177
|
+
onChange: changeTab,
|
|
178
|
+
steps: steps,
|
|
179
|
+
value: tab
|
|
180
|
+
}),
|
|
181
|
+
/*#__PURE__*/ _jsxs("section", {
|
|
182
|
+
"aria-labelledby": connectionTabId(tab),
|
|
183
|
+
className: theme.layout.main,
|
|
184
|
+
id: connectionTabPanelId(tab),
|
|
185
|
+
role: "tabpanel",
|
|
186
|
+
children: [
|
|
187
|
+
tab === 'connection' ? /*#__PURE__*/ _jsx(ConnectionSettingsTab, {
|
|
188
|
+
actions: actions,
|
|
189
|
+
onSaved: handleSourceSaved
|
|
190
|
+
}) : null,
|
|
191
|
+
tab === 'endpoints' ? /*#__PURE__*/ _jsx(EndpointsTab, {
|
|
192
|
+
actions: actions
|
|
193
|
+
}) : null,
|
|
194
|
+
tab === 'mappings' ? /*#__PURE__*/ _jsx(MappingsTab, {
|
|
195
|
+
actions: actions
|
|
196
|
+
}) : null,
|
|
197
|
+
tab === 'runs' ? /*#__PURE__*/ _jsx(RunsTab, {
|
|
198
|
+
actions: actions
|
|
199
|
+
}) : null
|
|
200
|
+
]
|
|
201
|
+
})
|
|
202
|
+
]
|
|
203
|
+
}) : // Not a dead end. With nothing selected the only useful thing on this
|
|
204
|
+
// screen is creating a connection, so that is what it shows — the
|
|
205
|
+
// previous message told an operator to select from a list that is
|
|
206
|
+
// empty on a fresh install, with no way to add the first one.
|
|
207
|
+
/*#__PURE__*/ _jsxs(_Fragment, {
|
|
208
|
+
children: [
|
|
209
|
+
/*#__PURE__*/ _jsx("p", {
|
|
210
|
+
className: theme.list.empty,
|
|
211
|
+
children: "Select a connection on the left, or set up a new one here."
|
|
212
|
+
}),
|
|
213
|
+
/*#__PURE__*/ _jsx(ConnectionSettingsTab, {
|
|
214
|
+
actions: actions,
|
|
215
|
+
onSaved: handleSourceSaved
|
|
216
|
+
})
|
|
217
|
+
]
|
|
117
218
|
})
|
|
118
219
|
})
|
|
119
220
|
]
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
className: theme.layout.split,
|
|
124
|
-
ref: contentRef,
|
|
125
|
-
children: [
|
|
126
|
-
/*#__PURE__*/ _jsx("aside", {
|
|
127
|
-
"aria-label": "Connections",
|
|
128
|
-
className: theme.layout.aside,
|
|
129
|
-
children: /*#__PURE__*/ _jsx(ConnectionList, {
|
|
130
|
-
actions: actions,
|
|
131
|
-
reloadToken: sourcesReloadToken
|
|
132
|
-
})
|
|
133
|
-
}),
|
|
134
|
-
/*#__PURE__*/ _jsx("section", {
|
|
135
|
-
"aria-label": "Connection detail",
|
|
136
|
-
className: theme.layout.main,
|
|
137
|
-
children: hasConnection ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
138
|
-
children: [
|
|
139
|
-
/*#__PURE__*/ _jsx(ConnectionTabs, {
|
|
140
|
-
counts: {
|
|
141
|
-
endpoints: store.endpointsTotal,
|
|
142
|
-
mappings: store.mappingsTotal,
|
|
143
|
-
runs: store.runsTotal
|
|
144
|
-
},
|
|
145
|
-
onChange: changeTab,
|
|
146
|
-
value: tab
|
|
147
|
-
}),
|
|
148
|
-
/*#__PURE__*/ _jsxs("section", {
|
|
149
|
-
"aria-labelledby": connectionTabId(tab),
|
|
150
|
-
className: theme.layout.main,
|
|
151
|
-
id: connectionTabPanelId(tab),
|
|
152
|
-
role: "tabpanel",
|
|
153
|
-
children: [
|
|
154
|
-
tab === 'connection' ? /*#__PURE__*/ _jsx(ConnectionSettingsTab, {
|
|
155
|
-
actions: actions,
|
|
156
|
-
onSaved: handleSourceSaved
|
|
157
|
-
}) : null,
|
|
158
|
-
tab === 'endpoints' ? /*#__PURE__*/ _jsx(EndpointsTab, {
|
|
159
|
-
actions: actions
|
|
160
|
-
}) : null,
|
|
161
|
-
tab === 'mappings' ? /*#__PURE__*/ _jsx(MappingsTab, {
|
|
162
|
-
actions: actions
|
|
163
|
-
}) : null,
|
|
164
|
-
tab === 'runs' ? /*#__PURE__*/ _jsx(RunsTab, {
|
|
165
|
-
actions: actions
|
|
166
|
-
}) : null
|
|
167
|
-
]
|
|
168
|
-
})
|
|
169
|
-
]
|
|
170
|
-
}) : // Not a dead end. With nothing selected the only useful thing on this
|
|
171
|
-
// screen is creating a connection, so that is what it shows — the
|
|
172
|
-
// previous message told an operator to select from a list that is
|
|
173
|
-
// empty on a fresh install, with no way to add the first one.
|
|
174
|
-
/*#__PURE__*/ _jsxs(_Fragment, {
|
|
175
|
-
children: [
|
|
176
|
-
/*#__PURE__*/ _jsx("p", {
|
|
177
|
-
className: theme.list.empty,
|
|
178
|
-
children: "Select a connection on the left, or set up a new one here."
|
|
179
|
-
}),
|
|
180
|
-
/*#__PURE__*/ _jsx(ConnectionSettingsTab, {
|
|
181
|
-
actions: actions,
|
|
182
|
-
onSaved: handleSourceSaved
|
|
183
|
-
})
|
|
184
|
-
]
|
|
185
|
-
})
|
|
186
|
-
})
|
|
187
|
-
]
|
|
188
|
-
})
|
|
189
|
-
]
|
|
221
|
+
})
|
|
222
|
+
]
|
|
223
|
+
})
|
|
190
224
|
});
|
|
191
225
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useEffectEvent } from 'react';
|
|
4
4
|
import { useIntegrationsStore } from '../store';
|
|
5
|
+
import { useLabels } from '../text/context';
|
|
5
6
|
import { integrationsPageTheme } from '../theme';
|
|
6
7
|
import { Notice } from './Primitives';
|
|
7
8
|
import { RunHistoryRow } from './RunHistoryRow';
|
|
@@ -17,6 +18,7 @@ import { useInfiniteList } from './useInfiniteList';
|
|
|
17
18
|
const PAGE_SIZE = 20;
|
|
18
19
|
export function RunHistory({ actions, mappingId, refreshKey, onRefresh }) {
|
|
19
20
|
const store = useIntegrationsStore();
|
|
21
|
+
const labels = useLabels();
|
|
20
22
|
const list = useInfiniteList({
|
|
21
23
|
load: (page)=>actions.listRuns({
|
|
22
24
|
page,
|
|
@@ -69,7 +71,7 @@ export function RunHistory({ actions, mappingId, refreshKey, onRefresh }) {
|
|
|
69
71
|
children: [
|
|
70
72
|
/*#__PURE__*/ _jsx("h3", {
|
|
71
73
|
className: theme.card.title,
|
|
72
|
-
children:
|
|
74
|
+
children: labels.runs.historyHeading
|
|
73
75
|
}),
|
|
74
76
|
/*#__PURE__*/ _jsx("span", {
|
|
75
77
|
className: theme.field.hint,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useEffectEvent, useState } from 'react';
|
|
4
4
|
import { useIntegrationsStore } from '../store';
|
|
5
|
+
import { useLabels } from '../text/context';
|
|
5
6
|
import { integrationsPageTheme } from '../theme';
|
|
6
7
|
import { Notice } from './Primitives';
|
|
7
8
|
import { RunConfirmStep } from './RunConfirmStep';
|
|
@@ -19,6 +20,7 @@ import { StatGridSkeleton } from './Skeletons';
|
|
|
19
20
|
*/ const theme = integrationsPageTheme;
|
|
20
21
|
export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }) {
|
|
21
22
|
const store = useIntegrationsStore();
|
|
23
|
+
const labels = useLabels();
|
|
22
24
|
const [confirming, setConfirming] = useState(false);
|
|
23
25
|
const [starting, setStarting] = useState(false);
|
|
24
26
|
const [cancelling, setCancelling] = useState(false);
|
|
@@ -103,7 +105,7 @@ export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }
|
|
|
103
105
|
}),
|
|
104
106
|
/*#__PURE__*/ _jsx("p", {
|
|
105
107
|
className: theme.field.hint,
|
|
106
|
-
children:
|
|
108
|
+
children: labels.runs.summary(mapping.targetEntity, mapping.writeMode, mapping.missingRecordPolicy)
|
|
107
109
|
})
|
|
108
110
|
]
|
|
109
111
|
}),
|
|
@@ -150,7 +152,7 @@ export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }
|
|
|
150
152
|
}) : null,
|
|
151
153
|
!(store.planning || plan || activeRunId) ? /*#__PURE__*/ _jsx(Notice, {
|
|
152
154
|
tone: "info",
|
|
153
|
-
title:
|
|
155
|
+
title: labels.runs.previewExplainTitle,
|
|
154
156
|
children: "A preview reads the source without writing anything, and reports how many rows would be created, updated, skipped, and how many rows in the target the source no longer returns."
|
|
155
157
|
}) : null
|
|
156
158
|
]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { cn } from '../../../utils/cn';
|
|
4
|
+
import { useLabels } from '../text/context';
|
|
4
5
|
import { integrationsPageTheme } from '../theme';
|
|
5
6
|
/**
|
|
6
7
|
* The records the planner could not use, folded away.
|
|
@@ -18,13 +19,14 @@ import { integrationsPageTheme } from '../theme';
|
|
|
18
19
|
}).join(', ');
|
|
19
20
|
}
|
|
20
21
|
export function RunPlanIssues({ issues }) {
|
|
22
|
+
const labels = useLabels();
|
|
21
23
|
if (issues.length === 0) return null;
|
|
22
24
|
return /*#__PURE__*/ _jsxs("details", {
|
|
23
25
|
className: cn(theme.card.wrapper, 'px-3 py-2.5 sm:px-4 sm:py-3'),
|
|
24
26
|
children: [
|
|
25
27
|
/*#__PURE__*/ _jsx("summary", {
|
|
26
28
|
className: cn(theme.card.title, 'cursor-pointer'),
|
|
27
|
-
children:
|
|
29
|
+
children: labels.runs.unusable(issues.length)
|
|
28
30
|
}),
|
|
29
31
|
/*#__PURE__*/ _jsx("ul", {
|
|
30
32
|
className: "mt-2 flex flex-col gap-2",
|
|
@@ -41,15 +43,15 @@ export function RunPlanIssues({ issues }) {
|
|
|
41
43
|
}),
|
|
42
44
|
issue.dedupKey ? /*#__PURE__*/ _jsx("span", {
|
|
43
45
|
className: theme.field.hint,
|
|
44
|
-
children:
|
|
46
|
+
children: `${labels.runs.dedupKey}: ${issue.dedupKey}`
|
|
45
47
|
}) : null,
|
|
46
48
|
issue.keptRow ? /*#__PURE__*/ _jsx("span", {
|
|
47
49
|
className: theme.field.hint,
|
|
48
|
-
children:
|
|
50
|
+
children: `${labels.runs.kept} — ${describeRow(issue.keptRow)}`
|
|
49
51
|
}) : null,
|
|
50
52
|
issue.row ? /*#__PURE__*/ _jsx("span", {
|
|
51
53
|
className: theme.field.hint,
|
|
52
|
-
children: issue.keptRow ?
|
|
54
|
+
children: issue.keptRow ? `${labels.runs.skipped} — ${describeRow(issue.row)}` : describeRow(issue.row)
|
|
53
55
|
}) : null
|
|
54
56
|
]
|
|
55
57
|
}, `${issue.sourceIndex}-${issue.kind}-${index}`))
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useLabels } from '../text/context';
|
|
3
4
|
import { integrationsPageTheme } from '../theme';
|
|
4
5
|
import { Badge, Notice, Stat } from './Primitives';
|
|
5
6
|
import { RunPlanIssues } from './RunPlanIssues';
|
|
@@ -17,6 +18,7 @@ import { RunPlanIssues } from './RunPlanIssues';
|
|
|
17
18
|
return undefined;
|
|
18
19
|
}
|
|
19
20
|
export function RunPlanSummary({ plan }) {
|
|
21
|
+
const labels = useLabels();
|
|
20
22
|
// A source that reports a total and hands over a different number of records
|
|
21
23
|
// is the loudest symptom of a wrong pagination profile, and the run that
|
|
22
24
|
// follows treats everything it never saw as missing.
|
|
@@ -54,28 +56,28 @@ export function RunPlanSummary({ plan }) {
|
|
|
54
56
|
className: theme.stat.grid,
|
|
55
57
|
children: [
|
|
56
58
|
/*#__PURE__*/ _jsx(Stat, {
|
|
57
|
-
label:
|
|
59
|
+
label: labels.runs.toCreate,
|
|
58
60
|
value: plan.toCreate
|
|
59
61
|
}),
|
|
60
62
|
/*#__PURE__*/ _jsx(Stat, {
|
|
61
|
-
label:
|
|
63
|
+
label: labels.runs.toUpdate,
|
|
62
64
|
value: plan.toUpdate
|
|
63
65
|
}),
|
|
64
66
|
/*#__PURE__*/ _jsx(Stat, {
|
|
65
|
-
label:
|
|
67
|
+
label: labels.runs.toSkip,
|
|
66
68
|
value: plan.toSkip
|
|
67
69
|
}),
|
|
68
70
|
/*#__PURE__*/ _jsx(Stat, {
|
|
69
|
-
label:
|
|
71
|
+
label: labels.runs.missing,
|
|
70
72
|
tone: missingTone(plan),
|
|
71
73
|
value: plan.missing
|
|
72
74
|
}),
|
|
73
75
|
/*#__PURE__*/ _jsx(Stat, {
|
|
74
|
-
label:
|
|
76
|
+
label: labels.runs.alreadyThere,
|
|
75
77
|
value: plan.existingCount
|
|
76
78
|
}),
|
|
77
79
|
/*#__PURE__*/ _jsx(Stat, {
|
|
78
|
-
label:
|
|
80
|
+
label: labels.runs.fetched,
|
|
79
81
|
value: plan.totalFetched
|
|
80
82
|
})
|
|
81
83
|
]
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { useIntegrationsStore } from '../store';
|
|
5
|
+
import { useLabels } from '../text/context';
|
|
5
6
|
import { Notice } from './Primitives';
|
|
6
7
|
import { RunHistory } from './RunHistory';
|
|
7
8
|
import { RunPanel } from './RunPanel';
|
|
8
9
|
export function RunsTab({ actions, onSubscribeProgress }) {
|
|
9
10
|
const store = useIntegrationsStore();
|
|
11
|
+
const labels = useLabels();
|
|
10
12
|
// A lookup by id, not a list operation: the selected mapping is on the page the
|
|
11
13
|
// server already returned, and nothing here filters, sorts or pages it.
|
|
12
14
|
const selectedId = store.selectedMappingId;
|
|
@@ -25,7 +27,7 @@ export function RunsTab({ actions, onSubscribeProgress }) {
|
|
|
25
27
|
onSubscribeProgress: onSubscribeProgress
|
|
26
28
|
}) : /*#__PURE__*/ _jsx(Notice, {
|
|
27
29
|
tone: "info",
|
|
28
|
-
title:
|
|
30
|
+
title: labels.runs.chooseMappingTitle,
|
|
29
31
|
children: "A mapping is what says which endpoint feeds which target entity, and how missing rows are treated. Pick one on the Mappings tab; the history below still lists every run of this connection."
|
|
30
32
|
}),
|
|
31
33
|
/*#__PURE__*/ _jsx(RunHistory, {
|
|
@@ -13,6 +13,8 @@ export type { InfiniteListPage, UseInfiniteListOptions, UseInfiniteListResult, }
|
|
|
13
13
|
export { useInfiniteList } from './components/useInfiniteList';
|
|
14
14
|
export { useIntegrationsStore } from './store';
|
|
15
15
|
export type { ConnectionTab } from './store/state';
|
|
16
|
+
export type { IntegrationsLabels, IntegrationsLabelsOverride } from './text';
|
|
17
|
+
export { defaultIntegrationsLabels, resolveIntegrationsLabels } from './text';
|
|
16
18
|
export type { IntegrationsPageTheme } from './theme';
|
|
17
19
|
export { extendIntegrationsTheme, integrationsPageTheme } from './theme';
|
|
18
20
|
export type * from './types';
|
|
@@ -8,4 +8,5 @@ export { RunsTab } from './components/RunsTab';
|
|
|
8
8
|
export { CardSkeleton, ListSkeleton, StatGridSkeleton } from './components/Skeletons';
|
|
9
9
|
export { useInfiniteList } from './components/useInfiniteList';
|
|
10
10
|
export { useIntegrationsStore } from './store';
|
|
11
|
+
export { defaultIntegrationsLabels, resolveIntegrationsLabels } from './text';
|
|
11
12
|
export { extendIntegrationsTheme, integrationsPageTheme } from './theme';
|
|
@@ -85,6 +85,12 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
|
|
|
85
85
|
// A plan belongs to the mapping it was computed for. Showing it against a
|
|
86
86
|
// different one would have an operator approve counts from another import.
|
|
87
87
|
store.plan = null;
|
|
88
|
+
// And so does a RUN, which says something stronger than a plan does: that
|
|
89
|
+
// this import happened. Seen live — the Ünvanlar run stayed on screen
|
|
90
|
+
// under a header reading "Writes into locations", for a mapping that had
|
|
91
|
+
// never been run at all.
|
|
92
|
+
store.activeRunId = null;
|
|
93
|
+
store.progress = null;
|
|
88
94
|
}),
|
|
89
95
|
setRuns: (store)=>(items, total)=>batch(()=>{
|
|
90
96
|
store.runs = items;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type IntegrationsLabels } from './index';
|
|
2
|
+
export declare function IntegrationsLabelsProvider({ labels, children, }: {
|
|
3
|
+
labels: IntegrationsLabels;
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
/** The resolved labels. Falls back to English when no provider is above. */
|
|
7
|
+
export declare function useLabels(): IntegrationsLabels;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
import { defaultIntegrationsLabels } from './index';
|
|
5
|
+
/**
|
|
6
|
+
* The panel's words, reachable from any depth without prop-drilling.
|
|
7
|
+
*
|
|
8
|
+
* A context rather than a prop because the strings are needed by controls four
|
|
9
|
+
* levels down — a field row, a badge, a stat cell — and threading them through
|
|
10
|
+
* would put a `labels` prop on components that otherwise take none.
|
|
11
|
+
*/ const LabelsContext = /*#__PURE__*/ createContext(defaultIntegrationsLabels);
|
|
12
|
+
export function IntegrationsLabelsProvider({ labels, children }) {
|
|
13
|
+
return /*#__PURE__*/ _jsx(LabelsContext.Provider, {
|
|
14
|
+
value: labels,
|
|
15
|
+
children: children
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** The resolved labels. Falls back to English when no provider is above. */ export function useLabels() {
|
|
19
|
+
return useContext(LabelsContext);
|
|
20
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every word the panel says, in one place — and overridable by the app.
|
|
3
|
+
*
|
|
4
|
+
* The theme could already be replaced slice by slice; the text could not, so a
|
|
5
|
+
* Turkish intranet rendered a Turkish page header above an entirely English
|
|
6
|
+
* panel. From the operator's side that does not read as "in another language",
|
|
7
|
+
* it reads as no explanation at all: `upsert`, `Deduplication column`,
|
|
8
|
+
* `missing rows: ignore` mean nothing to the person whose job this screen is.
|
|
9
|
+
*
|
|
10
|
+
* English stays the default so an app that says nothing still gets sentences,
|
|
11
|
+
* and every group is overridable on its own — an app translating the steps does
|
|
12
|
+
* not have to restate the buttons.
|
|
13
|
+
*/
|
|
14
|
+
export type IntegrationsLabels = {
|
|
15
|
+
/** The four phases of the work, in the order they happen. */
|
|
16
|
+
journey: {
|
|
17
|
+
heading: string;
|
|
18
|
+
/** Shown once, above the steps: what this screen is for. */
|
|
19
|
+
intro: string;
|
|
20
|
+
connection: {
|
|
21
|
+
name: string;
|
|
22
|
+
purpose: string;
|
|
23
|
+
};
|
|
24
|
+
endpoints: {
|
|
25
|
+
name: string;
|
|
26
|
+
purpose: string;
|
|
27
|
+
};
|
|
28
|
+
mappings: {
|
|
29
|
+
name: string;
|
|
30
|
+
purpose: string;
|
|
31
|
+
};
|
|
32
|
+
runs: {
|
|
33
|
+
name: string;
|
|
34
|
+
purpose: string;
|
|
35
|
+
};
|
|
36
|
+
/** Marks a step whose prerequisite is not done yet. */
|
|
37
|
+
lockedHint: string;
|
|
38
|
+
doneHint: string;
|
|
39
|
+
/** One short phrase per step proving it is done. */
|
|
40
|
+
evidence: {
|
|
41
|
+
connection: (name: string) => string;
|
|
42
|
+
endpoints: (total: number, sampled: number) => string;
|
|
43
|
+
mappings: (total: number) => string;
|
|
44
|
+
runs: (total: number) => string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
connections: {
|
|
48
|
+
heading: string;
|
|
49
|
+
countOne: string;
|
|
50
|
+
countMany: (n: number) => string;
|
|
51
|
+
searchLabel: string;
|
|
52
|
+
searchPlaceholder: string;
|
|
53
|
+
create: string;
|
|
54
|
+
empty: string;
|
|
55
|
+
emptySearch: (term: string) => string;
|
|
56
|
+
loadFailed: string;
|
|
57
|
+
retry: string;
|
|
58
|
+
off: string;
|
|
59
|
+
};
|
|
60
|
+
runs: {
|
|
61
|
+
/** The one-line summary above the buttons. */
|
|
62
|
+
summary: (target: string, writeMode: string, missing: string) => string;
|
|
63
|
+
preview: string;
|
|
64
|
+
start: string;
|
|
65
|
+
cancel: string;
|
|
66
|
+
previewExplainTitle: string;
|
|
67
|
+
previewExplainBody: string;
|
|
68
|
+
historyHeading: string;
|
|
69
|
+
historyEmpty: string;
|
|
70
|
+
chooseMappingTitle: string;
|
|
71
|
+
chooseMappingBody: string;
|
|
72
|
+
toCreate: string;
|
|
73
|
+
toUpdate: string;
|
|
74
|
+
toSkip: string;
|
|
75
|
+
missing: string;
|
|
76
|
+
alreadyThere: string;
|
|
77
|
+
fetched: string;
|
|
78
|
+
unusable: (n: number) => string;
|
|
79
|
+
kept: string;
|
|
80
|
+
skipped: string;
|
|
81
|
+
dedupKey: string;
|
|
82
|
+
};
|
|
83
|
+
writeMode: {
|
|
84
|
+
upsert: string;
|
|
85
|
+
append: string;
|
|
86
|
+
replace: string;
|
|
87
|
+
};
|
|
88
|
+
missingPolicy: {
|
|
89
|
+
ignore: string;
|
|
90
|
+
deactivate: string;
|
|
91
|
+
delete: string;
|
|
92
|
+
};
|
|
93
|
+
common: {
|
|
94
|
+
save: string;
|
|
95
|
+
cancel: string;
|
|
96
|
+
delete: string;
|
|
97
|
+
deleting: string;
|
|
98
|
+
saving: string;
|
|
99
|
+
somethingWentWrong: string;
|
|
100
|
+
dismiss: string;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
/** What every app gets before it says otherwise. */
|
|
104
|
+
export declare const defaultIntegrationsLabels: IntegrationsLabels;
|
|
105
|
+
type DeepPartial<T> = {
|
|
106
|
+
[K in keyof T]?: T[K] extends (...args: never[]) => unknown ? T[K] : T[K] extends object ? DeepPartial<T[K]> : T[K];
|
|
107
|
+
};
|
|
108
|
+
export type IntegrationsLabelsOverride = DeepPartial<IntegrationsLabels>;
|
|
109
|
+
/**
|
|
110
|
+
* Fills the gaps in an app's labels from the defaults.
|
|
111
|
+
*
|
|
112
|
+
* Two levels deep is everything this shape has, so a group is merged rather
|
|
113
|
+
* than replaced — an app naming the four steps does not lose the sentences that
|
|
114
|
+
* explain them.
|
|
115
|
+
*/
|
|
116
|
+
export declare function resolveIntegrationsLabels(overrides?: IntegrationsLabelsOverride): IntegrationsLabels;
|
|
117
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every word the panel says, in one place — and overridable by the app.
|
|
3
|
+
*
|
|
4
|
+
* The theme could already be replaced slice by slice; the text could not, so a
|
|
5
|
+
* Turkish intranet rendered a Turkish page header above an entirely English
|
|
6
|
+
* panel. From the operator's side that does not read as "in another language",
|
|
7
|
+
* it reads as no explanation at all: `upsert`, `Deduplication column`,
|
|
8
|
+
* `missing rows: ignore` mean nothing to the person whose job this screen is.
|
|
9
|
+
*
|
|
10
|
+
* English stays the default so an app that says nothing still gets sentences,
|
|
11
|
+
* and every group is overridable on its own — an app translating the steps does
|
|
12
|
+
* not have to restate the buttons.
|
|
13
|
+
*/ /** What every app gets before it says otherwise. */ export const defaultIntegrationsLabels = {
|
|
14
|
+
journey: {
|
|
15
|
+
heading: 'How an import works',
|
|
16
|
+
intro: 'Data is brought in from another service and written into one of your own tables. Four steps, in this order — each one is only useful once the one before it is done.',
|
|
17
|
+
connection: {
|
|
18
|
+
name: 'Connection',
|
|
19
|
+
purpose: 'Which service, at which address, and how we prove who we are.'
|
|
20
|
+
},
|
|
21
|
+
endpoints: {
|
|
22
|
+
name: 'Calls',
|
|
23
|
+
purpose: 'Which call to make, and what it really returns — try it and see.'
|
|
24
|
+
},
|
|
25
|
+
mappings: {
|
|
26
|
+
name: 'Mapping',
|
|
27
|
+
purpose: 'Which field from the answer lands in which column of your table.'
|
|
28
|
+
},
|
|
29
|
+
runs: {
|
|
30
|
+
name: 'Import',
|
|
31
|
+
purpose: 'See what would happen, then let it happen.'
|
|
32
|
+
},
|
|
33
|
+
lockedHint: 'Finish the step before this one first.',
|
|
34
|
+
doneHint: 'Done',
|
|
35
|
+
evidence: {
|
|
36
|
+
connection: (name)=>`Connected to ${name}`,
|
|
37
|
+
endpoints: (total, sampled)=>sampled === total ? `${total} call(s), all tried` : `${total} call(s), ${sampled} tried`,
|
|
38
|
+
mappings: (total)=>`${total} mapping(s)`,
|
|
39
|
+
runs: (total)=>`${total} import(s) run`
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
connections: {
|
|
43
|
+
heading: 'Connections',
|
|
44
|
+
countOne: '1 connection',
|
|
45
|
+
countMany: (n)=>`${n} connections`,
|
|
46
|
+
searchLabel: 'Search',
|
|
47
|
+
searchPlaceholder: 'Name or slug',
|
|
48
|
+
create: 'New connection',
|
|
49
|
+
empty: 'No connections yet. A connection is one service this app calls — its base URL, how it authenticates and how long it may take. Start with New connection.',
|
|
50
|
+
emptySearch: (term)=>`No connection matches “${term}”. Clear the search to see them all.`,
|
|
51
|
+
loadFailed: 'This list could not be loaded',
|
|
52
|
+
retry: 'Try again',
|
|
53
|
+
off: 'Off'
|
|
54
|
+
},
|
|
55
|
+
runs: {
|
|
56
|
+
summary: (target, writeMode, missing)=>`Writes into ${target} · ${writeMode} · missing rows: ${missing}`,
|
|
57
|
+
preview: 'Preview',
|
|
58
|
+
start: 'Run import',
|
|
59
|
+
cancel: 'Cancel',
|
|
60
|
+
previewExplainTitle: 'Preview before you run',
|
|
61
|
+
previewExplainBody: 'A preview reads the source without writing anything, and reports how many rows would be created, updated, skipped, and how many rows in the target the source no longer returns.',
|
|
62
|
+
historyHeading: 'History',
|
|
63
|
+
historyEmpty: 'Nothing has run yet. A preview writes nothing; the first entry appears here once an import is started.',
|
|
64
|
+
chooseMappingTitle: 'Choose a mapping to preview or run an import',
|
|
65
|
+
chooseMappingBody: 'A mapping is what says which endpoint feeds which target entity, and how missing rows are treated.',
|
|
66
|
+
toCreate: 'To create',
|
|
67
|
+
toUpdate: 'To update',
|
|
68
|
+
toSkip: 'To skip',
|
|
69
|
+
missing: 'Missing',
|
|
70
|
+
alreadyThere: 'Already there',
|
|
71
|
+
fetched: 'Fetched',
|
|
72
|
+
unusable: (n)=>`${n} record(s) the plan could not use`,
|
|
73
|
+
kept: 'Kept',
|
|
74
|
+
skipped: 'Skipped',
|
|
75
|
+
dedupKey: 'Dedup key'
|
|
76
|
+
},
|
|
77
|
+
writeMode: {
|
|
78
|
+
upsert: 'update what matches, insert the rest',
|
|
79
|
+
append: 'always insert, never match',
|
|
80
|
+
replace: 'empty the table, then insert'
|
|
81
|
+
},
|
|
82
|
+
missingPolicy: {
|
|
83
|
+
ignore: 'leave them exactly as they are',
|
|
84
|
+
deactivate: 'mark them inactive',
|
|
85
|
+
delete: 'delete them'
|
|
86
|
+
},
|
|
87
|
+
common: {
|
|
88
|
+
save: 'Save',
|
|
89
|
+
cancel: 'Cancel',
|
|
90
|
+
delete: 'Delete',
|
|
91
|
+
deleting: 'Deleting…',
|
|
92
|
+
saving: 'Saving…',
|
|
93
|
+
somethingWentWrong: 'Something went wrong',
|
|
94
|
+
dismiss: 'Dismiss error'
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Fills the gaps in an app's labels from the defaults.
|
|
99
|
+
*
|
|
100
|
+
* Two levels deep is everything this shape has, so a group is merged rather
|
|
101
|
+
* than replaced — an app naming the four steps does not lose the sentences that
|
|
102
|
+
* explain them.
|
|
103
|
+
*/ export function resolveIntegrationsLabels(overrides) {
|
|
104
|
+
if (!overrides) return defaultIntegrationsLabels;
|
|
105
|
+
const merged = {
|
|
106
|
+
...defaultIntegrationsLabels
|
|
107
|
+
};
|
|
108
|
+
for (const [group, value] of Object.entries(overrides)){
|
|
109
|
+
const base = defaultIntegrationsLabels[group];
|
|
110
|
+
if (!value || typeof value !== 'object' || !base || typeof base !== 'object') {
|
|
111
|
+
merged[group] = value ?? base;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const groupMerged = {
|
|
115
|
+
...base
|
|
116
|
+
};
|
|
117
|
+
for (const [key, inner] of Object.entries(value)){
|
|
118
|
+
const innerBase = groupMerged[key];
|
|
119
|
+
groupMerged[key] = inner && typeof inner === 'object' && innerBase && typeof innerBase === 'object' ? {
|
|
120
|
+
...innerBase,
|
|
121
|
+
...inner
|
|
122
|
+
} : inner ?? innerBase;
|
|
123
|
+
}
|
|
124
|
+
merged[group] = groupMerged;
|
|
125
|
+
}
|
|
126
|
+
return merged;
|
|
127
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { defaultIntegrationsLabels, resolveIntegrationsLabels } from './index';
|
|
3
|
+
/**
|
|
4
|
+
* The panel shipped English inside a Turkish app, which does not read as
|
|
5
|
+
* "another language" to the person using it — it reads as no explanation at
|
|
6
|
+
* all. Apps can now say it their own way, and the point of merging rather than
|
|
7
|
+
* replacing is that translating one group never blanks the rest.
|
|
8
|
+
*/ describe('resolveIntegrationsLabels', ()=>{
|
|
9
|
+
test('saying nothing gets the English defaults, not empty strings', ()=>{
|
|
10
|
+
expect(resolveIntegrationsLabels()).toBe(defaultIntegrationsLabels);
|
|
11
|
+
expect(resolveIntegrationsLabels(undefined).connections.heading).toBe('Connections');
|
|
12
|
+
});
|
|
13
|
+
test('overriding one key inside a group keeps that group’s other keys', ()=>{
|
|
14
|
+
const labels = resolveIntegrationsLabels({
|
|
15
|
+
connections: {
|
|
16
|
+
heading: 'Bağlantılar'
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
expect(labels.connections.heading).toBe('Bağlantılar');
|
|
20
|
+
expect(labels.connections.searchLabel).toBe(defaultIntegrationsLabels.connections.searchLabel);
|
|
21
|
+
expect(labels.runs.preview).toBe(defaultIntegrationsLabels.runs.preview);
|
|
22
|
+
});
|
|
23
|
+
test('a nested group merges too — naming a step keeps its explanation', ()=>{
|
|
24
|
+
const labels = resolveIntegrationsLabels({
|
|
25
|
+
journey: {
|
|
26
|
+
connection: {
|
|
27
|
+
name: 'Bağlantı'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
expect(labels.journey.connection.name).toBe('Bağlantı');
|
|
32
|
+
expect(labels.journey.connection.purpose).toBe(defaultIntegrationsLabels.journey.connection.purpose);
|
|
33
|
+
expect(labels.journey.endpoints.name).toBe(defaultIntegrationsLabels.journey.endpoints.name);
|
|
34
|
+
});
|
|
35
|
+
test('a function label can be replaced, and is called with the same arguments', ()=>{
|
|
36
|
+
const labels = resolveIntegrationsLabels({
|
|
37
|
+
connections: {
|
|
38
|
+
countMany: (n)=>`${n} bağlantı`
|
|
39
|
+
},
|
|
40
|
+
runs: {
|
|
41
|
+
summary: (t)=>`${t} tablosuna yazar`
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
expect(labels.connections.countMany(3)).toBe('3 bağlantı');
|
|
45
|
+
expect(labels.runs.summary('departments', 'upsert', 'ignore')).toBe('departments tablosuna yazar');
|
|
46
|
+
});
|
|
47
|
+
test('the defaults are never mutated by an override', ()=>{
|
|
48
|
+
resolveIntegrationsLabels({
|
|
49
|
+
connections: {
|
|
50
|
+
heading: 'Bağlantılar'
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
expect(defaultIntegrationsLabels.connections.heading).toBe('Connections');
|
|
54
|
+
});
|
|
55
|
+
test('every step has both a name and a sentence saying what it is for', ()=>{
|
|
56
|
+
const { journey } = defaultIntegrationsLabels;
|
|
57
|
+
for (const step of [
|
|
58
|
+
journey.connection,
|
|
59
|
+
journey.endpoints,
|
|
60
|
+
journey.mappings,
|
|
61
|
+
journey.runs
|
|
62
|
+
]){
|
|
63
|
+
expect(step.name.length).toBeGreaterThan(0);
|
|
64
|
+
expect(step.purpose.length).toBeGreaterThan(10);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -43,6 +43,22 @@ export type IntegrationsPageTheme = {
|
|
|
43
43
|
active: string;
|
|
44
44
|
inactive: string;
|
|
45
45
|
};
|
|
46
|
+
/** The four phases of an import, as a strip of steps rather than flat tabs. */
|
|
47
|
+
steps: {
|
|
48
|
+
intro: string;
|
|
49
|
+
wrapper: string;
|
|
50
|
+
item: string;
|
|
51
|
+
itemActive: string;
|
|
52
|
+
itemDone: string;
|
|
53
|
+
itemLocked: string;
|
|
54
|
+
marker: string;
|
|
55
|
+
markerActive: string;
|
|
56
|
+
markerDone: string;
|
|
57
|
+
markerLocked: string;
|
|
58
|
+
name: string;
|
|
59
|
+
purpose: string;
|
|
60
|
+
evidence: string;
|
|
61
|
+
};
|
|
46
62
|
field: {
|
|
47
63
|
wrapper: string;
|
|
48
64
|
label: string;
|
|
@@ -46,6 +46,24 @@ export const integrationsPageTheme = {
|
|
|
46
46
|
active: 'bg-white text-slate-900 shadow-sm dark:bg-slate-800 dark:text-slate-100',
|
|
47
47
|
inactive: 'text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-100'
|
|
48
48
|
},
|
|
49
|
+
// A step is a card, not a tab: it has to carry a name, a sentence saying what
|
|
50
|
+
// the step is for, and a line of evidence that it is done. Four of those do
|
|
51
|
+
// not fit on a phone side by side, so they stack and become a row at sm.
|
|
52
|
+
steps: {
|
|
53
|
+
intro: 'text-slate-600 text-sm leading-relaxed sm:max-w-3xl dark:text-slate-400',
|
|
54
|
+
wrapper: 'grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-4',
|
|
55
|
+
item: 'flex min-w-0 items-start gap-2.5 rounded-xl border border-slate-200 bg-white px-3 py-2.5 text-left transition-colors hover:border-slate-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-sky-500 focus-visible:outline-offset-2 dark:border-slate-800 dark:bg-slate-900 dark:hover:border-slate-700',
|
|
56
|
+
itemActive: 'border-sky-400 bg-sky-50 ring-1 ring-sky-400 hover:border-sky-400 dark:border-sky-600 dark:bg-sky-950 dark:ring-sky-600',
|
|
57
|
+
itemDone: 'border-emerald-200 dark:border-emerald-900',
|
|
58
|
+
itemLocked: 'opacity-70',
|
|
59
|
+
marker: 'flex size-6 shrink-0 items-center justify-center rounded-full border font-semibold text-[11px] tabular-nums',
|
|
60
|
+
markerActive: 'border-sky-500 bg-sky-500 text-white dark:border-sky-500 dark:bg-sky-500',
|
|
61
|
+
markerDone: 'border-emerald-300 bg-emerald-100 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-400',
|
|
62
|
+
markerLocked: 'border-slate-200 bg-slate-100 text-slate-400 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-500',
|
|
63
|
+
name: 'font-semibold text-slate-900 text-sm dark:text-slate-100',
|
|
64
|
+
purpose: 'mt-0.5 text-slate-600 text-xs leading-snug dark:text-slate-400',
|
|
65
|
+
evidence: 'mt-1 font-medium text-[11px] text-emerald-700 dark:text-emerald-400'
|
|
66
|
+
},
|
|
49
67
|
field: {
|
|
50
68
|
wrapper: 'flex flex-col gap-1.5',
|
|
51
69
|
label: 'font-medium text-slate-700 text-xs dark:text-slate-300',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FilterCondition, SortCondition } from '../../../types';
|
|
2
|
+
import type { IntegrationsLabelsOverride } from '../text';
|
|
2
3
|
import type { IntegrationEndpoint, IntegrationMapping, IntegrationRun, IntegrationSource } from './records';
|
|
3
4
|
import type { ConnectionTestValue, EndpointSampleValue, RunPlanValue, RunProgressValue } from './results';
|
|
4
5
|
/**
|
|
@@ -84,6 +85,14 @@ export type IntegrationsPageProps = {
|
|
|
84
85
|
subtitle?: string;
|
|
85
86
|
className?: string;
|
|
86
87
|
actions: IntegrationsActions;
|
|
88
|
+
/**
|
|
89
|
+
* The panel's own words, in the app's language.
|
|
90
|
+
*
|
|
91
|
+
* Anything left out falls back to English. Without this the panel was English
|
|
92
|
+
* inside apps that are not — which does not read as "another language" to the
|
|
93
|
+
* person using it, it reads as no explanation at all.
|
|
94
|
+
*/
|
|
95
|
+
labels?: IntegrationsLabelsOverride;
|
|
87
96
|
/** Realtime run progress, when the app wires the pub/sub topic. */
|
|
88
97
|
onSubscribeProgress?: (handler: (progress: RunProgressValue) => void) => () => void;
|
|
89
98
|
};
|
package/dist/fe/index.d.ts
CHANGED
|
@@ -21,8 +21,9 @@ export type { ForgotPasswordAction, ForgotPasswordFormProps, ForgotPasswordHeade
|
|
|
21
21
|
export { ForgotPasswordPage, useForgotPasswordStore, } from './components/ForgotPasswordPage';
|
|
22
22
|
export type { FormBuilderProps, FormFieldConfig, FormFieldProps, FormFieldRenderProps, FormFooterRenderProps, FormLayout, FormMode, FormSize, FormState, UseFormBuilderOptions, UseFormBuilderReturn, } from './components/FormBuilder';
|
|
23
23
|
export { FormBuilder, FormField, formBuilderTheme, useFormBuilder, } from './components/FormBuilder';
|
|
24
|
-
export { extendIntegrationsTheme, IntegrationsPage, integrationsPageTheme, useIntegrationsStore, } from './components/IntegrationsPage';
|
|
24
|
+
export { defaultIntegrationsLabels, extendIntegrationsTheme, IntegrationsPage, integrationsPageTheme, resolveIntegrationsLabels, useIntegrationsStore, } from './components/IntegrationsPage';
|
|
25
25
|
export type { ConnectionTab } from './components/IntegrationsPage/store/state';
|
|
26
|
+
export type { IntegrationsLabels, IntegrationsLabelsOverride, } from './components/IntegrationsPage/text';
|
|
26
27
|
export type { IntegrationsPageTheme } from './components/IntegrationsPage/theme';
|
|
27
28
|
export type * from './components/IntegrationsPage/types';
|
|
28
29
|
export type { LoginAction, LoginFeatureConfig, LoginFormData, LoginFormProps, LoginHeaderProps, LoginPageConfig, LoginPageProps, LoginPageVariant, LogoutAction, MeAction, } from './components/LoginPage';
|
package/dist/fe/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export { ComponentCanvas, ComponentSidebar, createDefaultRegistry, DesignSystemP
|
|
|
15
15
|
export { DeviceCard, DevicesHeader, DevicesPage, useDevicesStore } from './components/DevicesPage';
|
|
16
16
|
export { ForgotPasswordPage, useForgotPasswordStore } from './components/ForgotPasswordPage';
|
|
17
17
|
export { FormBuilder, FormField, formBuilderTheme, useFormBuilder } from './components/FormBuilder';
|
|
18
|
-
export { extendIntegrationsTheme, IntegrationsPage, integrationsPageTheme, useIntegrationsStore } from './components/IntegrationsPage';
|
|
18
|
+
export { defaultIntegrationsLabels, extendIntegrationsTheme, IntegrationsPage, integrationsPageTheme, resolveIntegrationsLabels, useIntegrationsStore } from './components/IntegrationsPage';
|
|
19
19
|
export { LoginPage, useLoginStore } from './components/LoginPage';
|
|
20
20
|
export { MagicLinkVerifyPage, magicLinkVerifyPageTheme, useMagicLinkVerifyStore } from './components/MagicLinkVerifyPage';
|
|
21
21
|
export { extendNotificationCenterTheme, NotificationBell, NotificationCenter, notificationCenterTheme, useNotificationCenterStore } from './components/NotificationCenter';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.800",
|
|
4
4
|
"description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
|
|
5
5
|
"author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|