@finos/legend-application-marketplace 0.1.51 → 0.1.53

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.
Files changed (64) hide show
  1. package/lib/__lib__/LegendMarketplaceAppEvent.d.ts +2 -1
  2. package/lib/__lib__/LegendMarketplaceAppEvent.d.ts.map +1 -1
  3. package/lib/__lib__/LegendMarketplaceAppEvent.js +1 -0
  4. package/lib/__lib__/LegendMarketplaceAppEvent.js.map +1 -1
  5. package/lib/components/AddToCart/RecommendedAddOnsModal.d.ts.map +1 -1
  6. package/lib/components/AddToCart/RecommendedAddOnsModal.js +32 -6
  7. package/lib/components/AddToCart/RecommendedAddOnsModal.js.map +1 -1
  8. package/lib/components/AddToCart/RecommendedItemsCard.d.ts +0 -1
  9. package/lib/components/AddToCart/RecommendedItemsCard.d.ts.map +1 -1
  10. package/lib/components/AddToCart/RecommendedItemsCard.js +12 -17
  11. package/lib/components/AddToCart/RecommendedItemsCard.js.map +1 -1
  12. package/lib/components/Header/LegendMarketplaceAppInfo.d.ts.map +1 -1
  13. package/lib/components/Header/LegendMarketplaceAppInfo.js +55 -12
  14. package/lib/components/Header/LegendMarketplaceAppInfo.js.map +1 -1
  15. package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.d.ts.map +1 -1
  16. package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.js +2 -2
  17. package/lib/components/ProviderCard/LegendMarketplaceTerminalCard.js.map +1 -1
  18. package/lib/components/orders/CancelOrderDialog.d.ts +27 -0
  19. package/lib/components/orders/CancelOrderDialog.d.ts.map +1 -0
  20. package/lib/components/orders/CancelOrderDialog.js +52 -0
  21. package/lib/components/orders/CancelOrderDialog.js.map +1 -0
  22. package/lib/components/orders/ProgressTracker.d.ts +23 -0
  23. package/lib/components/orders/ProgressTracker.d.ts.map +1 -0
  24. package/lib/components/orders/ProgressTracker.js +116 -0
  25. package/lib/components/orders/ProgressTracker.js.map +1 -0
  26. package/lib/index.css +2 -2
  27. package/lib/index.css.map +1 -1
  28. package/lib/package.json +1 -1
  29. package/lib/pages/Profile/LegendMarketplaceYourOrders.d.ts.map +1 -1
  30. package/lib/pages/Profile/LegendMarketplaceYourOrders.js +43 -36
  31. package/lib/pages/Profile/LegendMarketplaceYourOrders.js.map +1 -1
  32. package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.d.ts.map +1 -1
  33. package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.js +42 -18
  34. package/lib/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.js.map +1 -1
  35. package/lib/stores/LegendMarketPlaceVendorDataStore.d.ts +10 -4
  36. package/lib/stores/LegendMarketPlaceVendorDataStore.d.ts.map +1 -1
  37. package/lib/stores/LegendMarketPlaceVendorDataStore.js +82 -49
  38. package/lib/stores/LegendMarketPlaceVendorDataStore.js.map +1 -1
  39. package/lib/stores/cart/CartStore.d.ts.map +1 -1
  40. package/lib/stores/cart/CartStore.js +2 -1
  41. package/lib/stores/cart/CartStore.js.map +1 -1
  42. package/lib/stores/orders/OrderHelpers.d.ts +49 -0
  43. package/lib/stores/orders/OrderHelpers.d.ts.map +1 -0
  44. package/lib/stores/orders/OrderHelpers.js +134 -0
  45. package/lib/stores/orders/OrderHelpers.js.map +1 -0
  46. package/lib/stores/orders/OrderStore.d.ts +2 -0
  47. package/lib/stores/orders/OrderStore.d.ts.map +1 -1
  48. package/lib/stores/orders/OrderStore.js +31 -0
  49. package/lib/stores/orders/OrderStore.js.map +1 -1
  50. package/package.json +13 -13
  51. package/src/__lib__/LegendMarketplaceAppEvent.ts +1 -0
  52. package/src/components/AddToCart/RecommendedAddOnsModal.tsx +145 -17
  53. package/src/components/AddToCart/RecommendedItemsCard.tsx +32 -62
  54. package/src/components/Header/LegendMarketplaceAppInfo.tsx +104 -53
  55. package/src/components/ProviderCard/LegendMarketplaceTerminalCard.tsx +18 -2
  56. package/src/components/orders/CancelOrderDialog.tsx +157 -0
  57. package/src/components/orders/ProgressTracker.tsx +266 -0
  58. package/src/pages/Profile/LegendMarketplaceYourOrders.tsx +227 -230
  59. package/src/pages/TerminalsAddons/LegendMarketplaceTerminalsAddons.tsx +140 -17
  60. package/src/stores/LegendMarketPlaceVendorDataStore.tsx +114 -84
  61. package/src/stores/cart/CartStore.ts +2 -1
  62. package/src/stores/orders/OrderHelpers.ts +167 -0
  63. package/src/stores/orders/OrderStore.ts +51 -0
  64. package/tsconfig.json +3 -0
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import React, { useCallback, useEffect } from 'react';
17
+ import React, { useCallback, useEffect, useState } from 'react';
18
18
  import { observer } from 'mobx-react-lite';
19
19
  import {
20
20
  Box,
@@ -24,218 +24,229 @@ import {
24
24
  Chip,
25
25
  CircularProgress,
26
26
  Button,
27
- Table,
28
- TableBody,
29
- TableCell,
30
- TableContainer,
31
- TableHead,
32
- TableRow,
33
- Paper,
34
- IconButton,
35
- Collapse,
27
+ Accordion,
28
+ AccordionSummary,
29
+ AccordionDetails,
30
+ Stack,
31
+ Tooltip,
36
32
  } from '@mui/material';
37
33
  import { flowResult } from 'mobx';
38
34
  import {
39
- RefreshIcon,
40
35
  ShoppingCartIcon,
41
- ClockIcon,
42
- CheckCircleIcon,
43
36
  ChevronDownIcon,
44
- ChevronRightIcon,
37
+ TimesCircleIcon,
45
38
  } from '@finos/legend-art';
46
39
  import { LegendMarketplacePage } from '../LegendMarketplacePage.js';
47
40
  import { useLegendMarketplaceBaseStore } from '../../application/providers/LegendMarketplaceFrameworkProvider.js';
48
41
  import {
49
42
  type TerminalProductOrder,
50
- OrderCategory,
51
43
  OrderStatus,
52
44
  } from '@finos/legend-server-marketplace';
53
- import { assertErrorThrown } from '@finos/legend-shared';
45
+ import { assertErrorThrown, isNullable } from '@finos/legend-shared';
54
46
  import {
55
47
  useLegendMarketplaceOrdersStore,
56
48
  withLegendMarketplaceOrdersStore,
57
49
  } from '../../application/providers/LegendMarketplaceYourOrdersStoreProvider.js';
50
+ import { ProgressTracker } from '../../components/orders/ProgressTracker.js';
51
+ import { CancelOrderDialog } from '../../components/orders/CancelOrderDialog.js';
52
+ import {
53
+ formatOrderDate,
54
+ canCancelOrder,
55
+ } from '../../stores/orders/OrderHelpers.js';
58
56
 
59
- const OrderStatusChip: React.FC<{ status: OrderStatus | undefined }> = ({
60
- status,
61
- }) => {
62
- switch (status) {
63
- case OrderStatus.IN_PROGRESS:
64
- return (
65
- <Chip
66
- icon={<ClockIcon />}
67
- label={status.toString()}
68
- variant="filled"
69
- size="small"
70
- className={'order-status-chip--open'}
71
- />
72
- );
73
- case OrderStatus.OPEN:
74
- return (
75
- <Chip
76
- icon={<ClockIcon />}
77
- label={status.toString()}
78
- variant="filled"
79
- size="small"
80
- className={'order-status-chip--open'}
81
- />
82
- );
83
- case OrderStatus.COMPLETED:
84
- return (
85
- <Chip
86
- icon={<CheckCircleIcon />}
87
- label={status.toString()}
88
- variant="filled"
89
- size="small"
90
- className={'order-status-chip--closed'}
91
- />
92
- );
93
- default:
94
- return (
95
- <Chip
96
- icon={<CheckCircleIcon />}
97
- label={OrderStatus.IN_PROGRESS.toString()}
98
- variant="filled"
99
- size="small"
100
- className={'order-status-chip--open'}
101
- />
102
- );
103
- }
104
- };
57
+ const OrderAccordion: React.FC<{
58
+ order: TerminalProductOrder;
59
+ isOpenOrder: boolean;
60
+ }> = observer(({ order, isOpenOrder }) => {
61
+ const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
62
+ const ordersStore = useLegendMarketplaceOrdersStore();
105
63
 
106
- const StageChip: React.FC<{ stage: string | null }> = ({ stage }) => {
107
- if (!stage) {
108
- return <Typography className="order-row__stage">Pending</Typography>;
109
- }
64
+ const isCancellable = canCancelOrder(order);
110
65
 
111
- return <Typography className="order-row__stage">{stage}</Typography>;
112
- };
66
+ const handleCancelClick = (): void => {
67
+ setCancelDialogOpen(true);
68
+ };
113
69
 
114
- const OrderTableRow: React.FC<{ order: TerminalProductOrder }> = observer(
115
- ({ order }) => {
116
- const [expanded, setExpanded] = React.useState(false);
70
+ const formatCurrency = (
71
+ amount: number | string | null | undefined,
72
+ ): string => {
73
+ const numAmount =
74
+ isNullable(amount) || amount === 'null'
75
+ ? 0
76
+ : typeof amount === 'string'
77
+ ? parseFloat(amount)
78
+ : amount;
79
+ return numAmount.toLocaleString('en-US', {
80
+ style: 'currency',
81
+ currency: 'USD',
82
+ });
83
+ };
117
84
 
118
- return (
119
- <>
120
- <TableRow onClick={() => setExpanded(!expanded)}>
121
- <TableCell>
122
- <Box className="order-row__id-cell">
123
- <IconButton size="small" className="expand-button">
124
- {expanded ? (
125
- <ChevronDownIcon size={16} />
126
- ) : (
127
- <ChevronRightIcon size={16} />
128
- )}
129
- </IconButton>
130
- <Typography className="order-id">#{order.order_id}</Typography>
85
+ return (
86
+ <>
87
+ <Accordion
88
+ defaultExpanded={true}
89
+ sx={{ '&:before': { display: 'none' }, mb: 2 }}
90
+ >
91
+ <AccordionSummary
92
+ expandIcon={<ChevronDownIcon />}
93
+ aria-controls={`${order.order_id}-content`}
94
+ id={`${order.order_id}-header`}
95
+ className="legend-marketplace-order-accordion__summary"
96
+ >
97
+ <Box className="legend-marketplace-order-accordion__summary-content">
98
+ <Box className="legend-marketplace-order-accordion__summary-field">
99
+ <Typography
100
+ variant="caption"
101
+ className="legend-marketplace-order-accordion__summary-label"
102
+ >
103
+ Order Placed
104
+ </Typography>
105
+ <Typography
106
+ variant="body2"
107
+ className="legend-marketplace-order-accordion__summary-value"
108
+ >
109
+ {formatOrderDate(order.created_at)}
110
+ </Typography>
131
111
  </Box>
132
- </TableCell>
133
112
 
134
- <TableCell>
135
- <OrderStatusChip status={order.workflow_details?.workflow_status} />
136
- </TableCell>
113
+ <Box className="legend-marketplace-order-accordion__summary-field">
114
+ <Typography
115
+ variant="caption"
116
+ className="legend-marketplace-order-accordion__summary-label"
117
+ >
118
+ Total
119
+ </Typography>
120
+ <Typography
121
+ variant="body2"
122
+ className="legend-marketplace-order-accordion__summary-value"
123
+ >
124
+ {formatCurrency(order.order_cost)}
125
+ </Typography>
126
+ </Box>
137
127
 
138
- <TableCell>
139
- <Typography className="order-row__date">
140
- {order.created_at.split('T')[0]}
141
- </Typography>
142
- </TableCell>
128
+ <Box className="legend-marketplace-order-accordion__summary-field">
129
+ <Typography
130
+ variant="caption"
131
+ className="legend-marketplace-order-accordion__summary-label"
132
+ >
133
+ Order #
134
+ </Typography>
135
+ <Typography
136
+ variant="body2"
137
+ className="legend-marketplace-order-accordion__summary-value"
138
+ >
139
+ {order.order_id}
140
+ </Typography>
141
+ </Box>
143
142
 
144
- <TableCell align="center">
145
- <Typography className="order-row__items-count">
146
- {order.service_pricing_items.length}
147
- </Typography>
148
- </TableCell>
143
+ {isOpenOrder && (
144
+ <Box className="legend-marketplace-order-accordion__summary-actions">
145
+ <Tooltip
146
+ title={
147
+ !isCancellable
148
+ ? 'Order cancellation is only available during approval stages'
149
+ : ''
150
+ }
151
+ arrow={true}
152
+ >
153
+ <Button
154
+ variant="outlined"
155
+ size="small"
156
+ startIcon={<TimesCircleIcon />}
157
+ disabled={!isCancellable}
158
+ onClick={(e) => {
159
+ e.stopPropagation();
160
+ handleCancelClick();
161
+ }}
162
+ className="legend-marketplace-order-accordion__cancel-button"
163
+ >
164
+ Cancel Order
165
+ </Button>
166
+ </Tooltip>
167
+ </Box>
168
+ )}
169
+ </Box>
170
+ </AccordionSummary>
149
171
 
150
- <TableCell align="right">
151
- <Typography className="order-row__total-cost">
152
- {order.order_cost}
153
- </Typography>
154
- </TableCell>
172
+ <AccordionDetails className="legend-marketplace-order-accordion__details">
173
+ <Box className="legend-marketplace-order-accordion__details-container">
174
+ <Box className="legend-marketplace-order-accordion__items-section">
175
+ <Stack spacing={2}>
176
+ {order.service_pricing_items.map((item, index) => (
177
+ <Box
178
+ key={item.entity_id}
179
+ className="legend-marketplace-order-accordion__item"
180
+ >
181
+ <Box className="legend-marketplace-order-accordion__vendor-chips-row">
182
+ <Typography
183
+ variant="caption"
184
+ className="legend-marketplace-order-accordion__vendor-name"
185
+ >
186
+ {order.vendor_name}
187
+ </Typography>
188
+ <Stack
189
+ direction="row"
190
+ spacing={1}
191
+ className="legend-marketplace-order-accordion__chips-container"
192
+ >
193
+ <Chip
194
+ label={item.entity_type}
195
+ size="small"
196
+ className={
197
+ item.entity_id === order.vendor_profile_id
198
+ ? 'legend-marketplace-order-accordion__chip-terminal'
199
+ : 'legend-marketplace-order-accordion__chip-addon'
200
+ }
201
+ />
202
+ <Chip
203
+ label={item.entity_category}
204
+ size="small"
205
+ className="legend-marketplace-order-accordion__chip-category"
206
+ />
207
+ <Chip
208
+ label={`${formatCurrency(item.entity_cost)} per month`}
209
+ size="small"
210
+ className="legend-marketplace-order-accordion__chip-price"
211
+ />
212
+ </Stack>
213
+ </Box>
214
+ <Typography
215
+ variant="h6"
216
+ className="legend-marketplace-order-accordion__product-name"
217
+ >
218
+ {item.entity_name}
219
+ </Typography>
220
+ {index === order.service_pricing_items.length - 1 &&
221
+ order.business_justification && (
222
+ <Typography
223
+ variant="body2"
224
+ className="legend-marketplace-order-accordion__business-justification"
225
+ >
226
+ Business Justification: {order.business_justification}
227
+ </Typography>
228
+ )}
229
+ </Box>
230
+ ))}
231
+ </Stack>
232
+ </Box>
155
233
 
156
- <TableCell align="center">
157
- <StageChip stage={order.workflow_details?.current_stage ?? null} />
158
- </TableCell>
159
- </TableRow>
234
+ <Box className="legend-marketplace-order-accordion__progress-tracker-section">
235
+ {order.workflow_details && <ProgressTracker order={order} />}
236
+ </Box>
237
+ </Box>
238
+ </AccordionDetails>
239
+ </Accordion>
160
240
 
161
- <TableRow>
162
- <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={7}>
163
- <Collapse in={expanded} timeout="auto" unmountOnExit={true}>
164
- <Box className="order-details">
165
- <Typography className="order-details__title">
166
- Order Items ({order.service_pricing_items.length})
167
- </Typography>
168
- <Box className="order-details__items-table">
169
- <Table size="medium">
170
- <TableHead>
171
- <TableRow>
172
- <TableCell align="left">
173
- <Typography>Product</Typography>
174
- </TableCell>
175
- <TableCell align="left">
176
- <Typography>Provider</Typography>
177
- </TableCell>
178
- <TableCell align="left">
179
- <Typography>Category</Typography>
180
- </TableCell>
181
- </TableRow>
182
- </TableHead>
183
- <TableBody>
184
- {order.order_category === OrderCategory.TERMINAL ? (
185
- <TableRow key={`${order.vendor_profile_id}`}>
186
- <TableCell align="left">
187
- <Typography className="order-details__product-name">
188
- {order.vendor_profile_name}
189
- </Typography>
190
- </TableCell>
191
- <TableCell align="left">
192
- <Typography className="order-details__provider-name">
193
- {order.vendor_name}
194
- </Typography>
195
- </TableCell>
196
- <TableCell align="left">
197
- <Typography className="order-details__category-name">
198
- {'Terminal'}
199
- </Typography>
200
- </TableCell>
201
- </TableRow>
202
- ) : (
203
- order.service_pricing_items.map((item, index) => (
204
- <TableRow
205
- key={`${item.service_pricing_id || `item-${item.service_pricing_name}`}-${order.order_id}`}
206
- >
207
- <TableCell align="left">
208
- <Typography className="order-details__product-name">
209
- {item.service_pricing_name}
210
- </Typography>
211
- </TableCell>
212
- <TableCell align="left">
213
- <Typography className="order-details__provider-name">
214
- {order.vendor_name}
215
- </Typography>
216
- </TableCell>
217
- <TableCell align="left">
218
- <Typography className="order-details__category-name">
219
- {item.service_pricing_id ===
220
- order.vendor_profile_id
221
- ? 'Terminal'
222
- : 'Add-on'}
223
- </Typography>
224
- </TableCell>
225
- </TableRow>
226
- ))
227
- )}
228
- </TableBody>
229
- </Table>
230
- </Box>
231
- </Box>
232
- </Collapse>
233
- </TableCell>
234
- </TableRow>
235
- </>
236
- );
237
- },
238
- );
241
+ <CancelOrderDialog
242
+ open={cancelDialogOpen}
243
+ onClose={() => setCancelDialogOpen(false)}
244
+ order={order}
245
+ orderStore={ordersStore}
246
+ />
247
+ </>
248
+ );
249
+ });
239
250
 
240
251
  export const LegendMarketplaceYourOrders: React.FC =
241
252
  withLegendMarketplaceOrdersStore(
@@ -243,7 +254,6 @@ export const LegendMarketplaceYourOrders: React.FC =
243
254
  const baseStore = useLegendMarketplaceBaseStore();
244
255
  const ordersStore = useLegendMarketplaceOrdersStore();
245
256
 
246
- // Helper function to safely execute flow operations
247
257
  const executeFlowSafely = useCallback(
248
258
  (flowFn: () => Generator<Promise<unknown>, void, unknown>) => {
249
259
  flowResult(flowFn()).catch((error) => {
@@ -272,12 +282,7 @@ export const LegendMarketplaceYourOrders: React.FC =
272
282
  [ordersStore, executeFlowSafely],
273
283
  );
274
284
 
275
- const handleRefresh = useCallback(() => {
276
- executeFlowSafely(() => ordersStore.refreshCurrentOrders());
277
- }, [ordersStore, executeFlowSafely]);
278
-
279
285
  useEffect(() => {
280
- // Load open orders by default
281
286
  if (ordersStore.openOrders.length === 0) {
282
287
  executeFlowSafely(() => ordersStore.fetchOpenOrders());
283
288
  }
@@ -291,20 +296,9 @@ export const LegendMarketplaceYourOrders: React.FC =
291
296
  <Box className="legend-marketplace-your-orders__content">
292
297
  <Box className="legend-marketplace-your-orders__header-section">
293
298
  <Typography variant="h1">Your Orders</Typography>
294
- <Button
295
- variant="outlined"
296
- startIcon={
297
- isLoading ? <CircularProgress size={25} /> : <RefreshIcon />
298
- }
299
- onClick={handleRefresh}
300
- disabled={isLoading}
301
- className="legend-marketplace-your-orders__refresh-button"
302
- >
303
- {isLoading ? 'Refreshing...' : 'Refresh'}
304
- </Button>
305
299
  </Box>
306
300
 
307
- <Box className="orders-tabs">
301
+ <Box className="legend-marketplace-your-orders__tabs">
308
302
  <Tabs
309
303
  value={ordersStore.selectedTab}
310
304
  onChange={handleTabChange}
@@ -322,49 +316,52 @@ export const LegendMarketplaceYourOrders: React.FC =
322
316
  </Box>
323
317
 
324
318
  {isLoading ? (
325
- <Box className="orders-loading">
319
+ <Box className="legend-marketplace-your-orders__loading">
326
320
  <CircularProgress size={40} />
327
- <Typography className="loading-text">
321
+ <Typography className="legend-marketplace-your-orders__loading-text">
328
322
  Loading your orders...
329
323
  </Typography>
330
324
  </Box>
331
325
  ) : currentOrders.length === 0 ? (
332
- <Box className="orders-empty">
333
- <ShoppingCartIcon size={48} className="empty-icon" />
334
- <Typography className="empty-title">
326
+ <Box className="legend-marketplace-your-orders__empty">
327
+ <ShoppingCartIcon
328
+ size={48}
329
+ className="legend-marketplace-your-orders__empty-icon"
330
+ />
331
+ <Typography
332
+ variant="h3"
333
+ className="legend-marketplace-your-orders__empty-title"
334
+ >
335
335
  No{' '}
336
336
  {ordersStore.selectedTab === 'open' ? 'active' : 'completed'}{' '}
337
337
  orders found
338
338
  </Typography>
339
- <Typography className="empty-description">
339
+ <Typography className="legend-marketplace-your-orders__empty-description">
340
340
  {ordersStore.selectedTab === 'open'
341
341
  ? "You don't have any orders in progress. Start shopping to place your first order!"
342
342
  : "You don't have any completed orders yet. Your completed orders will appear here."}
343
343
  </Typography>
344
344
  </Box>
345
345
  ) : (
346
- <TableContainer
347
- component={Paper}
348
- className="orders-table-container"
346
+ <Stack
347
+ spacing={2}
348
+ className="legend-marketplace-your-orders__orders-list"
349
349
  >
350
- <Table>
351
- <TableHead>
352
- <TableRow>
353
- <TableCell>Order ID</TableCell>
354
- <TableCell>Status</TableCell>
355
- <TableCell>Order Date</TableCell>
356
- <TableCell align="center">Items</TableCell>
357
- <TableCell align="right">Total Cost</TableCell>
358
- <TableCell align="center">Stage</TableCell>
359
- </TableRow>
360
- </TableHead>
361
- <TableBody>
362
- {currentOrders.map((order) => (
363
- <OrderTableRow key={order.order_id} order={order} />
364
- ))}
365
- </TableBody>
366
- </Table>
367
- </TableContainer>
350
+ {currentOrders.map((order) => {
351
+ const isOpenOrder =
352
+ order.workflow_details?.workflow_status ===
353
+ OrderStatus.IN_PROGRESS ||
354
+ order.workflow_details?.workflow_status ===
355
+ OrderStatus.OPEN;
356
+ return (
357
+ <OrderAccordion
358
+ key={order.order_id}
359
+ order={order}
360
+ isOpenOrder={isOpenOrder}
361
+ />
362
+ );
363
+ })}
364
+ </Stack>
368
365
  )}
369
366
  </Box>
370
367
  </LegendMarketplacePage>