@finos/legend-application-marketplace 0.2.20 → 0.2.21
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/lib/application/LegendMarketplaceApplicationConfig.d.ts +6 -3
- package/lib/application/LegendMarketplaceApplicationConfig.d.ts.map +1 -1
- package/lib/application/LegendMarketplaceApplicationConfig.js +13 -4
- package/lib/application/LegendMarketplaceApplicationConfig.js.map +1 -1
- package/lib/components/Pagination/PaginationControls.d.ts.map +1 -1
- package/lib/components/Pagination/PaginationControls.js +2 -2
- package/lib/components/Pagination/PaginationControls.js.map +1 -1
- package/lib/components/ProviderCard/LegendMarketplaceOrderProfileCard.d.ts +23 -0
- package/lib/components/ProviderCard/LegendMarketplaceOrderProfileCard.d.ts.map +1 -0
- package/lib/components/ProviderCard/LegendMarketplaceOrderProfileCard.js +93 -0
- package/lib/components/ProviderCard/LegendMarketplaceOrderProfileCard.js.map +1 -0
- package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.d.ts.map +1 -1
- package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.js +5 -11
- package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.js.map +1 -1
- package/lib/components/ProviderCard/OrderProfileDetailModal.d.ts +26 -0
- package/lib/components/ProviderCard/OrderProfileDetailModal.d.ts.map +1 -0
- package/lib/components/ProviderCard/OrderProfileDetailModal.js +36 -0
- package/lib/components/ProviderCard/OrderProfileDetailModal.js.map +1 -0
- package/lib/components/ProviderCard/OrderProfileMultiselectModal.d.ts +26 -0
- package/lib/components/ProviderCard/OrderProfileMultiselectModal.d.ts.map +1 -0
- package/lib/components/ProviderCard/OrderProfileMultiselectModal.js +31 -0
- package/lib/components/ProviderCard/OrderProfileMultiselectModal.js.map +1 -0
- package/lib/components/ProviderCard/orderProfileUtils.d.ts +71 -0
- package/lib/components/ProviderCard/orderProfileUtils.d.ts.map +1 -0
- package/lib/components/ProviderCard/orderProfileUtils.js +122 -0
- package/lib/components/ProviderCard/orderProfileUtils.js.map +1 -0
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.d.ts.map +1 -1
- package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.js +3 -0
- package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.js.map +1 -1
- package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.d.ts.map +1 -1
- package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.js +42 -9
- package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.js.map +1 -1
- package/lib/stores/LegendMarketPlaceVendorDataStore.d.ts +6 -2
- package/lib/stores/LegendMarketPlaceVendorDataStore.d.ts.map +1 -1
- package/lib/stores/LegendMarketPlaceVendorDataStore.js +25 -2
- package/lib/stores/LegendMarketPlaceVendorDataStore.js.map +1 -1
- package/lib/stores/LegendMarketplaceBaseStore.d.ts +1 -1
- package/lib/stores/LegendMarketplaceBaseStore.d.ts.map +1 -1
- package/lib/stores/LegendMarketplaceBaseStore.js +10 -5
- package/lib/stores/LegendMarketplaceBaseStore.js.map +1 -1
- package/lib/stores/ai/LegendMarketplaceAIChatStore.d.ts +10 -0
- package/lib/stores/ai/LegendMarketplaceAIChatStore.d.ts.map +1 -1
- package/lib/stores/ai/LegendMarketplaceAIChatStore.js +115 -50
- package/lib/stores/ai/LegendMarketplaceAIChatStore.js.map +1 -1
- package/lib/stores/cart/CartStore.d.ts +14 -2
- package/lib/stores/cart/CartStore.d.ts.map +1 -1
- package/lib/stores/cart/CartStore.js +68 -5
- package/lib/stores/cart/CartStore.js.map +1 -1
- package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.d.ts +2 -1
- package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.d.ts.map +1 -1
- package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.js +8 -3
- package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.js.map +1 -1
- package/package.json +10 -10
- package/src/application/LegendMarketplaceApplicationConfig.ts +19 -11
- package/src/components/Pagination/PaginationControls.tsx +19 -17
- package/src/components/ProviderCard/LegendMarketplaceOrderProfileCard.tsx +246 -0
- package/src/components/ProviderCard/LegendMarketplaceTerminalCard.tsx +9 -16
- package/src/components/ProviderCard/OrderProfileDetailModal.tsx +224 -0
- package/src/components/ProviderCard/OrderProfileMultiselectModal.tsx +142 -0
- package/src/components/ProviderCard/orderProfileUtils.ts +165 -0
- package/src/pages/Lakehouse/entitlements/PermitDataAccessRequest.tsx +3 -0
- package/src/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.tsx +170 -21
- package/src/stores/LegendMarketPlaceVendorDataStore.tsx +33 -1
- package/src/stores/LegendMarketplaceBaseStore.ts +13 -9
- package/src/stores/ai/LegendMarketplaceAIChatStore.ts +273 -69
- package/src/stores/cart/CartStore.ts +90 -4
- package/src/stores/lakehouse/entitlements/EntitlementsDashboardState.ts +10 -1
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026-present, Goldman Sachs
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { JSX } from 'react';
|
|
18
|
+
import { observer } from 'mobx-react-lite';
|
|
19
|
+
import { useLegendMarketplaceBaseStore } from '../../application/providers/LegendMarketplaceFrameworkProvider.js';
|
|
20
|
+
import {
|
|
21
|
+
Dialog,
|
|
22
|
+
DialogTitle,
|
|
23
|
+
DialogContent,
|
|
24
|
+
DialogActions,
|
|
25
|
+
Button,
|
|
26
|
+
Typography,
|
|
27
|
+
Box,
|
|
28
|
+
IconButton,
|
|
29
|
+
Chip,
|
|
30
|
+
Table,
|
|
31
|
+
TableBody,
|
|
32
|
+
TableCell,
|
|
33
|
+
TableContainer,
|
|
34
|
+
TableHead,
|
|
35
|
+
TableRow,
|
|
36
|
+
} from '@mui/material';
|
|
37
|
+
import { CloseIcon, DocumentIcon } from '@finos/legend-art';
|
|
38
|
+
import type { TraderProfile } from '@finos/legend-server-marketplace';
|
|
39
|
+
import {
|
|
40
|
+
formatItemPrice,
|
|
41
|
+
formatProfileSummaryLine,
|
|
42
|
+
getItemSummary,
|
|
43
|
+
groupOrderProfileItems,
|
|
44
|
+
OrderProfileLabel,
|
|
45
|
+
OrderProfileTableHeader,
|
|
46
|
+
} from './orderProfileUtils.js';
|
|
47
|
+
|
|
48
|
+
const CategoryChip = (props: {
|
|
49
|
+
category: string;
|
|
50
|
+
isTerminal: boolean;
|
|
51
|
+
}): JSX.Element => {
|
|
52
|
+
const { category, isTerminal } = props;
|
|
53
|
+
return (
|
|
54
|
+
<Chip
|
|
55
|
+
label={category}
|
|
56
|
+
size="small"
|
|
57
|
+
className={
|
|
58
|
+
isTerminal
|
|
59
|
+
? 'order-profile-modal__category-chip--terminal'
|
|
60
|
+
: 'order-profile-modal__category-chip--addon'
|
|
61
|
+
}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const OrderProfileDetailModal = observer(
|
|
67
|
+
(props: {
|
|
68
|
+
profile: TraderProfile;
|
|
69
|
+
open: boolean;
|
|
70
|
+
onClose: () => void;
|
|
71
|
+
multiselectTotalPrice?: number;
|
|
72
|
+
}): JSX.Element => {
|
|
73
|
+
const { profile, open, onClose, multiselectTotalPrice } = props;
|
|
74
|
+
const { cartStore } = useLegendMarketplaceBaseStore();
|
|
75
|
+
const items = profile.items;
|
|
76
|
+
const { terminalCount, addOnCount } = getItemSummary(items);
|
|
77
|
+
const groupedItems = groupOrderProfileItems(items);
|
|
78
|
+
const displayPrice =
|
|
79
|
+
profile.multiselect && multiselectTotalPrice !== undefined
|
|
80
|
+
? multiselectTotalPrice
|
|
81
|
+
: profile.price;
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<Dialog
|
|
85
|
+
open={open}
|
|
86
|
+
onClose={onClose}
|
|
87
|
+
maxWidth="md"
|
|
88
|
+
fullWidth={true}
|
|
89
|
+
className="order-profile-modal"
|
|
90
|
+
aria-labelledby="order-profile-modal-title"
|
|
91
|
+
>
|
|
92
|
+
<DialogTitle
|
|
93
|
+
id="order-profile-modal-title"
|
|
94
|
+
className="order-profile-modal__header"
|
|
95
|
+
>
|
|
96
|
+
<Box className="order-profile-modal__header-content">
|
|
97
|
+
<Box className="order-profile-modal__header-title">
|
|
98
|
+
<DocumentIcon className="order-profile-modal__header-icon" />
|
|
99
|
+
<Typography
|
|
100
|
+
variant="h6"
|
|
101
|
+
className="order-profile-modal__profile-name"
|
|
102
|
+
>
|
|
103
|
+
{profile.productName}
|
|
104
|
+
</Typography>
|
|
105
|
+
</Box>
|
|
106
|
+
<IconButton
|
|
107
|
+
onClick={onClose}
|
|
108
|
+
size="small"
|
|
109
|
+
aria-label="close"
|
|
110
|
+
className="order-profile-modal__close-button"
|
|
111
|
+
>
|
|
112
|
+
<CloseIcon />
|
|
113
|
+
</IconButton>
|
|
114
|
+
</Box>
|
|
115
|
+
<Typography
|
|
116
|
+
variant="body2"
|
|
117
|
+
className="order-profile-modal__header-summary"
|
|
118
|
+
>
|
|
119
|
+
{formatProfileSummaryLine(terminalCount, addOnCount)}
|
|
120
|
+
{OrderProfileLabel.PRICE_TOTAL_SEPARATOR}
|
|
121
|
+
<strong>{formatItemPrice(displayPrice)}</strong>
|
|
122
|
+
</Typography>
|
|
123
|
+
</DialogTitle>
|
|
124
|
+
|
|
125
|
+
<DialogContent className="order-profile-modal__content" dividers={true}>
|
|
126
|
+
<TableContainer>
|
|
127
|
+
<Table size="small" aria-label="order profile items">
|
|
128
|
+
<TableHead>
|
|
129
|
+
<TableRow className="order-profile-modal__table-head">
|
|
130
|
+
<TableCell className="order-profile-modal__table-header-cell">
|
|
131
|
+
{OrderProfileTableHeader.PRODUCT_NAME}
|
|
132
|
+
</TableCell>
|
|
133
|
+
<TableCell className="order-profile-modal__table-header-cell">
|
|
134
|
+
{OrderProfileTableHeader.PROVIDER}
|
|
135
|
+
</TableCell>
|
|
136
|
+
<TableCell className="order-profile-modal__table-header-cell">
|
|
137
|
+
{OrderProfileTableHeader.CATEGORY}
|
|
138
|
+
</TableCell>
|
|
139
|
+
<TableCell
|
|
140
|
+
align="center"
|
|
141
|
+
className="order-profile-modal__table-header-cell"
|
|
142
|
+
>
|
|
143
|
+
{OrderProfileTableHeader.COST_MONTHLY}
|
|
144
|
+
</TableCell>
|
|
145
|
+
</TableRow>
|
|
146
|
+
</TableHead>
|
|
147
|
+
<TableBody>
|
|
148
|
+
{groupedItems.map(({ item, isSubItem }) => {
|
|
149
|
+
const isInCart =
|
|
150
|
+
!item.isOwned && cartStore.isItemInCart(item.id);
|
|
151
|
+
const rowModifierClass = (() => {
|
|
152
|
+
if (item.isOwned) {
|
|
153
|
+
return 'order-profile-modal__table-row--owned';
|
|
154
|
+
}
|
|
155
|
+
if (isInCart) {
|
|
156
|
+
return 'order-profile-modal__table-row--in-cart';
|
|
157
|
+
}
|
|
158
|
+
return '';
|
|
159
|
+
})();
|
|
160
|
+
return (
|
|
161
|
+
<TableRow
|
|
162
|
+
key={item.id}
|
|
163
|
+
className={`order-profile-modal__table-row ${rowModifierClass}`}
|
|
164
|
+
>
|
|
165
|
+
<TableCell className="order-profile-modal__table-cell order-profile-modal__table-cell--name">
|
|
166
|
+
<Box
|
|
167
|
+
className={`order-profile-modal__product-name-wrapper ${isSubItem ? 'order-profile-modal__product-name-wrapper--sub' : ''}`}
|
|
168
|
+
>
|
|
169
|
+
<Box
|
|
170
|
+
className={`order-profile-modal__row-accent ${item.isTerminal ? 'order-profile-modal__row-accent--vendor-profile' : 'order-profile-modal__row-accent--addon'}`}
|
|
171
|
+
/>
|
|
172
|
+
<span>
|
|
173
|
+
{item.productName}
|
|
174
|
+
{item.isOwned && (
|
|
175
|
+
<span className="order-profile-modal__owned-label">
|
|
176
|
+
{' '}
|
|
177
|
+
{OrderProfileLabel.OWNED_SUFFIX}
|
|
178
|
+
</span>
|
|
179
|
+
)}
|
|
180
|
+
{isInCart && (
|
|
181
|
+
<span className="order-profile-modal__in-cart-label">
|
|
182
|
+
{' '}
|
|
183
|
+
{OrderProfileLabel.IN_CART_SUFFIX}
|
|
184
|
+
</span>
|
|
185
|
+
)}
|
|
186
|
+
</span>
|
|
187
|
+
</Box>
|
|
188
|
+
</TableCell>
|
|
189
|
+
<TableCell className="order-profile-modal__table-cell">
|
|
190
|
+
{item.providerName}
|
|
191
|
+
</TableCell>
|
|
192
|
+
<TableCell className="order-profile-modal__table-cell">
|
|
193
|
+
<CategoryChip
|
|
194
|
+
category={item.category}
|
|
195
|
+
isTerminal={item.isTerminal}
|
|
196
|
+
/>
|
|
197
|
+
</TableCell>
|
|
198
|
+
<TableCell
|
|
199
|
+
align="center"
|
|
200
|
+
className="order-profile-modal__table-cell order-profile-modal__table-cell--price"
|
|
201
|
+
>
|
|
202
|
+
{formatItemPrice(item.price)}
|
|
203
|
+
</TableCell>
|
|
204
|
+
</TableRow>
|
|
205
|
+
);
|
|
206
|
+
})}
|
|
207
|
+
</TableBody>
|
|
208
|
+
</Table>
|
|
209
|
+
</TableContainer>
|
|
210
|
+
</DialogContent>
|
|
211
|
+
|
|
212
|
+
<DialogActions className="order-profile-modal__actions">
|
|
213
|
+
<Button
|
|
214
|
+
variant="contained"
|
|
215
|
+
onClick={onClose}
|
|
216
|
+
className="order-profile-modal__close-btn"
|
|
217
|
+
>
|
|
218
|
+
{OrderProfileLabel.CLOSE}
|
|
219
|
+
</Button>
|
|
220
|
+
</DialogActions>
|
|
221
|
+
</Dialog>
|
|
222
|
+
);
|
|
223
|
+
},
|
|
224
|
+
);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026-present, Goldman Sachs
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type JSX, useCallback, useState } from 'react';
|
|
18
|
+
import {
|
|
19
|
+
Box,
|
|
20
|
+
Button,
|
|
21
|
+
Dialog,
|
|
22
|
+
DialogActions,
|
|
23
|
+
DialogContent,
|
|
24
|
+
DialogTitle,
|
|
25
|
+
List,
|
|
26
|
+
ListItemButton,
|
|
27
|
+
ListItemText,
|
|
28
|
+
Radio,
|
|
29
|
+
Typography,
|
|
30
|
+
} from '@mui/material';
|
|
31
|
+
import type {
|
|
32
|
+
TraderProfile,
|
|
33
|
+
TraderProfileItem,
|
|
34
|
+
} from '@finos/legend-server-marketplace';
|
|
35
|
+
import { observer } from 'mobx-react-lite';
|
|
36
|
+
import { formatItemPrice, OrderProfileLabel } from './orderProfileUtils.js';
|
|
37
|
+
|
|
38
|
+
export const OrderProfileMultiselectModal = observer(
|
|
39
|
+
(props: {
|
|
40
|
+
profile: TraderProfile;
|
|
41
|
+
open: boolean;
|
|
42
|
+
onClose: () => void;
|
|
43
|
+
onConfirm: (selectedTerminals: TraderProfileItem[]) => void;
|
|
44
|
+
}): JSX.Element => {
|
|
45
|
+
const { profile, open, onClose, onConfirm } = props;
|
|
46
|
+
const terminalItems = profile.items.filter(
|
|
47
|
+
(item) => item.isTerminal && !item.isOwned,
|
|
48
|
+
);
|
|
49
|
+
const [selectedId, setSelectedId] = useState<number | null>(null);
|
|
50
|
+
|
|
51
|
+
const handleConfirm = useCallback((): void => {
|
|
52
|
+
const selectedItem = terminalItems.find((item) => item.id === selectedId);
|
|
53
|
+
onConfirm(selectedItem ? [selectedItem] : []);
|
|
54
|
+
}, [terminalItems, selectedId, onConfirm]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Dialog
|
|
58
|
+
open={open}
|
|
59
|
+
onClose={onClose}
|
|
60
|
+
maxWidth="md"
|
|
61
|
+
fullWidth={true}
|
|
62
|
+
className="order-profile-multiselect-modal"
|
|
63
|
+
aria-labelledby="order-profile-multiselect-title"
|
|
64
|
+
>
|
|
65
|
+
<DialogTitle id="order-profile-multiselect-title">
|
|
66
|
+
{OrderProfileLabel.SELECT_TERMINAL_TITLE}
|
|
67
|
+
</DialogTitle>
|
|
68
|
+
<DialogContent dividers={true}>
|
|
69
|
+
<Typography
|
|
70
|
+
variant="body2"
|
|
71
|
+
className="order-profile-multiselect-modal__description"
|
|
72
|
+
>
|
|
73
|
+
Choose one terminal to include from{' '}
|
|
74
|
+
<strong>{profile.productName}</strong>.{' '}
|
|
75
|
+
{OrderProfileLabel.SELECT_TERMINAL_DESCRIPTION}
|
|
76
|
+
</Typography>
|
|
77
|
+
<List dense={false}>
|
|
78
|
+
{terminalItems.map((item) => (
|
|
79
|
+
<ListItemButton
|
|
80
|
+
key={item.id}
|
|
81
|
+
onClick={() => setSelectedId(item.id)}
|
|
82
|
+
selected={selectedId === item.id}
|
|
83
|
+
className={`order-profile-multiselect-modal__list-item ${selectedId === item.id ? 'order-profile-multiselect-modal__list-item--selected' : ''}`}
|
|
84
|
+
>
|
|
85
|
+
<Radio
|
|
86
|
+
checked={selectedId === item.id}
|
|
87
|
+
onChange={() => setSelectedId(item.id)}
|
|
88
|
+
size="small"
|
|
89
|
+
className="order-profile-multiselect-modal__radio"
|
|
90
|
+
inputProps={{ 'aria-labelledby': `terminal-item-${item.id}` }}
|
|
91
|
+
/>
|
|
92
|
+
<ListItemText
|
|
93
|
+
id={`terminal-item-${item.id}`}
|
|
94
|
+
disableTypography={true}
|
|
95
|
+
primary={
|
|
96
|
+
<Box className="order-profile-multiselect-modal__item-primary">
|
|
97
|
+
<Typography
|
|
98
|
+
variant="body1"
|
|
99
|
+
className="order-profile-multiselect-modal__item-name"
|
|
100
|
+
>
|
|
101
|
+
{item.productName}
|
|
102
|
+
</Typography>
|
|
103
|
+
<Typography
|
|
104
|
+
variant="body2"
|
|
105
|
+
className="order-profile-multiselect-modal__item-price"
|
|
106
|
+
>
|
|
107
|
+
{formatItemPrice(item.price)}
|
|
108
|
+
</Typography>
|
|
109
|
+
</Box>
|
|
110
|
+
}
|
|
111
|
+
secondary={
|
|
112
|
+
item.model !== null && item.model !== undefined ? (
|
|
113
|
+
<Typography
|
|
114
|
+
variant="caption"
|
|
115
|
+
className="order-profile-multiselect-modal__item-model"
|
|
116
|
+
>
|
|
117
|
+
{OrderProfileLabel.MODEL_PREFIX}
|
|
118
|
+
{item.model}
|
|
119
|
+
</Typography>
|
|
120
|
+
) : undefined
|
|
121
|
+
}
|
|
122
|
+
/>
|
|
123
|
+
</ListItemButton>
|
|
124
|
+
))}
|
|
125
|
+
</List>
|
|
126
|
+
</DialogContent>
|
|
127
|
+
<DialogActions>
|
|
128
|
+
<Button onClick={onClose} variant="outlined">
|
|
129
|
+
{OrderProfileLabel.CANCEL}
|
|
130
|
+
</Button>
|
|
131
|
+
<Button
|
|
132
|
+
onClick={handleConfirm}
|
|
133
|
+
variant="contained"
|
|
134
|
+
disabled={selectedId === null}
|
|
135
|
+
>
|
|
136
|
+
{OrderProfileLabel.ADD_TO_CART}
|
|
137
|
+
</Button>
|
|
138
|
+
</DialogActions>
|
|
139
|
+
</Dialog>
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026-present, Goldman Sachs
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { TraderProfileItem } from '@finos/legend-server-marketplace';
|
|
18
|
+
import { MAX_PRODUCT_IMAGE_COUNT } from '../../stores/lakehouse/dataProducts/ProductCardState.js';
|
|
19
|
+
|
|
20
|
+
// ─── String labels ───────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export enum OrderProfileLabel {
|
|
23
|
+
CHIP_LABEL = 'Order Profile',
|
|
24
|
+
ALREADY_HAVE_ACCESS = 'Already have access',
|
|
25
|
+
ADD_TO_CART = 'Add to cart',
|
|
26
|
+
IN_CART = 'In Cart',
|
|
27
|
+
ADDING = 'Adding...',
|
|
28
|
+
SELECT_TERMINAL_TITLE = 'Select Terminal',
|
|
29
|
+
SELECT_TERMINAL_DESCRIPTION = 'All Add-Ons will be added automatically after the terminal is confirmed.',
|
|
30
|
+
CANCEL = 'Cancel',
|
|
31
|
+
CLOSE = 'Close',
|
|
32
|
+
OWNED_SUFFIX = '(Owned)',
|
|
33
|
+
IN_CART_SUFFIX = '(In Cart)',
|
|
34
|
+
MODEL_PREFIX = 'Model: ',
|
|
35
|
+
PRICE_TOTAL_SEPARATOR = ' · Total: ',
|
|
36
|
+
VIEW_DETAILS = 'View details',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export enum OrderProfileTableHeader {
|
|
40
|
+
PRODUCT_NAME = 'PRODUCT NAME',
|
|
41
|
+
PROVIDER = 'PROVIDER',
|
|
42
|
+
CATEGORY = 'CATEGORY',
|
|
43
|
+
COST_MONTHLY = 'COST (Monthly)',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── Image URL ──────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns a stable random product image URL for a given asset base URL.
|
|
50
|
+
* Intended to be called once inside a `useState` initialiser so the image
|
|
51
|
+
* does not change on re-renders.
|
|
52
|
+
*/
|
|
53
|
+
export const getRandomImageUrl = (assetUrl: string): string => {
|
|
54
|
+
const randomValue = crypto.getRandomValues(new Uint32Array(1))[0] ?? 0;
|
|
55
|
+
const randomIndex = (randomValue % MAX_PRODUCT_IMAGE_COUNT) + 1;
|
|
56
|
+
return `${assetUrl}/images${randomIndex}.jpg`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// ─── Price formatting ─────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
const USD_FORMATTER = new Intl.NumberFormat('en-US', {
|
|
62
|
+
style: 'currency',
|
|
63
|
+
currency: 'USD',
|
|
64
|
+
minimumFractionDigits: 2,
|
|
65
|
+
maximumFractionDigits: 2,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/** Formats a price as a plain USD string, e.g. "$1,234.56". Used in detail tables. */
|
|
69
|
+
export const formatItemPrice = (price: number): string =>
|
|
70
|
+
USD_FORMATTER.format(price);
|
|
71
|
+
|
|
72
|
+
/** Formats a price with a "/month" suffix for display on cards. */
|
|
73
|
+
export const formatCardPrice = (price: number): string =>
|
|
74
|
+
`${USD_FORMATTER.format(price)}/month`;
|
|
75
|
+
|
|
76
|
+
// ─── Toast message formatters ────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
export const formatAddToCartSuccessMessage = (productName: string): string =>
|
|
79
|
+
`Order profile ${productName} has been successfully added to cart.`;
|
|
80
|
+
|
|
81
|
+
export const formatAddToCartErrorMessage = (
|
|
82
|
+
productName: string,
|
|
83
|
+
errorMessage: string,
|
|
84
|
+
): string => `Failed to add ${productName} to cart: ${errorMessage}`;
|
|
85
|
+
|
|
86
|
+
// ─── Item summary helpers ─────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
export const getItemSummary = (
|
|
89
|
+
items: TraderProfileItem[],
|
|
90
|
+
): { terminalCount: number; addOnCount: number } => {
|
|
91
|
+
const terminalCount = items.filter((item) => item.isTerminal).length;
|
|
92
|
+
return { terminalCount, addOnCount: items.length - terminalCount };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const formatProfileSummaryLine = (
|
|
96
|
+
terminalCount: number,
|
|
97
|
+
addOnCount: number,
|
|
98
|
+
): string => {
|
|
99
|
+
const terminalLabel =
|
|
100
|
+
terminalCount === 1 ? '1 Terminal' : `${terminalCount} Terminals`;
|
|
101
|
+
const addOnLabel = addOnCount === 1 ? '1 Add-On' : `${addOnCount} Add-Ons`;
|
|
102
|
+
return `${terminalLabel} · ${addOnLabel}`;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// ─── Multiselect price calculation ──────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Calculates the total price for a multiselect order profile.
|
|
109
|
+
* Finds the highest-priced terminal and sums it with its associated add-ons.
|
|
110
|
+
* Returns `undefined` when there are no terminal items.
|
|
111
|
+
*/
|
|
112
|
+
export const calculateMultiselectTotalPrice = (
|
|
113
|
+
items: TraderProfileItem[],
|
|
114
|
+
): number | undefined => {
|
|
115
|
+
const terminals = items.filter((item) => item.isTerminal && !item.isOwned);
|
|
116
|
+
if (terminals.length === 0) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
const highestTerminal = terminals.reduce((max, curr) =>
|
|
120
|
+
curr.price > max.price ? curr : max,
|
|
121
|
+
);
|
|
122
|
+
const addOns = items.filter(
|
|
123
|
+
(item) =>
|
|
124
|
+
!item.isTerminal && !item.isOwned && item.model === highestTerminal.model,
|
|
125
|
+
);
|
|
126
|
+
const addOnsTotal = addOns.reduce((sum, item) => sum + item.price, 0);
|
|
127
|
+
return highestTerminal.price + addOnsTotal;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// ─── Grouping ─────────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Groups items so that each vendor-profile (terminal) is immediately followed
|
|
134
|
+
* by its associated add-ons (matched by item.model). Unmatched add-ons are
|
|
135
|
+
* appended at the end.
|
|
136
|
+
*/
|
|
137
|
+
export const groupOrderProfileItems = (
|
|
138
|
+
items: TraderProfileItem[],
|
|
139
|
+
): { item: TraderProfileItem; isSubItem: boolean }[] => {
|
|
140
|
+
const result: { item: TraderProfileItem; isSubItem: boolean }[] = [];
|
|
141
|
+
|
|
142
|
+
const terminals = items.filter((i) => i.isTerminal);
|
|
143
|
+
const addOns = items.filter((i) => !i.isTerminal);
|
|
144
|
+
const matchedAddonIds = new Set<number>();
|
|
145
|
+
|
|
146
|
+
for (const terminal of terminals) {
|
|
147
|
+
result.push({ item: terminal, isSubItem: false });
|
|
148
|
+
if (terminal.model !== undefined && terminal.model !== null) {
|
|
149
|
+
for (const addon of addOns) {
|
|
150
|
+
if (addon.model === terminal.model && !matchedAddonIds.has(addon.id)) {
|
|
151
|
+
result.push({ item: addon, isSubItem: true });
|
|
152
|
+
matchedAddonIds.add(addon.id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (const addon of addOns) {
|
|
159
|
+
if (!matchedAddonIds.has(addon.id)) {
|
|
160
|
+
result.push({ item: addon, isSubItem: false });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
};
|
|
@@ -74,6 +74,9 @@ export const PermitDataAccessRequestTask =
|
|
|
74
74
|
marketplaceBaseStore.applicationStore.pluginManager;
|
|
75
75
|
const permitClient =
|
|
76
76
|
marketplaceBaseStore.permitWorkflowServerClient;
|
|
77
|
+
if (!permitClient) {
|
|
78
|
+
throw new Error('Permit workflow server is not configured');
|
|
79
|
+
}
|
|
77
80
|
|
|
78
81
|
const state = new PermitDataAccessRequestState(
|
|
79
82
|
dataAccessRequestId,
|