Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/open-actors-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@finos/legend-application-marketplace': patch
---

terminals and addons wokflow integration with view orders page
5 changes: 5 additions & 0 deletions .changeset/ripe-views-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@finos/legend-art': patch
---

added style variables for terminals and addons
5 changes: 5 additions & 0 deletions .changeset/swift-emus-go.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@finos/legend-server-marketplace': patch
---

added cart and order stores for terminals and addons
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ export enum LEGEND_MARKETPLACE_APP_EVENT {
CLICK_SUBSCRIBE_TO_NEWSLETTER = 'marketplace.click.subscribe.to.newsletter',
OPEN_INTEGRATED_PRODUCT = 'marketplace.open.integrated.product',
FETCH_PENDING_CONTRACT = 'marketplace.fetch.pending-contract.failure',
ORDER_CANCELLATION_FAILURE = 'marketplace.order.cancellation.failure',
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,32 @@
*/

import { observer } from 'mobx-react-lite';
import { useMemo, useState } from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Typography,
Grid,
Box,
IconButton,
TextField,
InputAdornment,
Select,
MenuItem,
FormControl,
InputLabel,
type SelectChangeEvent,
} from '@mui/material';
import {
CloseIcon,
CheckCircleIcon,
ArrowRightIcon,
WarningIcon,
SearchIcon,
ArrowUpIcon,
ArrowDownIcon,
} from '@finos/legend-art';
import {
TerminalItemType,
Expand Down Expand Up @@ -58,15 +68,47 @@ export const RecommendedAddOnsModal = observer(
onViewCart,
} = props;

const [searchTerm, setSearchTerm] = useState('');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | ''>('');

const filteredAndSortedItems = useMemo(() => {
let items = [...recommendedItems];

if (searchTerm) {
const search = searchTerm.toLowerCase();
items = items.filter(
(item) =>
item.productName.toLowerCase().includes(search) ||
item.providerName.toLowerCase().includes(search),
);
}

if (sortOrder) {
items.sort((a, b) =>
sortOrder === 'asc' ? a.price - b.price : b.price - a.price,
);
}

return items;
}, [recommendedItems, searchTerm, sortOrder]);

const closeModal = () => {
setShowModal(false);
setSearchTerm('');
setSortOrder('');
};

const handleViewCart = () => {
onViewCart?.();
closeModal();
};

const handleSortChange = (
event: SelectChangeEvent<'asc' | 'desc' | ''>,
) => {
setSortOrder(event.target.value);
};

if (!showModal) {
return null;
}
Expand Down Expand Up @@ -119,8 +161,10 @@ export const RecommendedAddOnsModal = observer(
className="recommended-addons-modal__section-title"
>
{terminal?.terminalItemType === TerminalItemType.TERMINAL
? 'Recommended Add-Ons'
: 'Recommended Terminals'}
? `Recommended Add-Ons for ${terminal.providerName}`
: terminal
? `Recommended Terminals for ${terminal.providerName}`
: ''}
</Typography>
<Typography
variant="body2"
Expand All @@ -141,24 +185,108 @@ export const RecommendedAddOnsModal = observer(
</Typography>
</Box>
) : (
<Grid container={true} spacing={2}>
{recommendedItems.map((item) => (
<Grid size={{ xs: 12, sm: 6 }} key={item.id}>
{terminal ? (
<RecommendedItemsCard
key={item.id}
vendorProfileId={terminal.id}
recommendedItem={item}
/>
) : (
<>
<Box className="recommended-addons-modal__filter-controls">
<TextField
size="medium"
placeholder="Search any service..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="recommended-addons-modal__search-field"
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">
<SearchIcon />
</InputAdornment>
),
},
}}
/>
<FormControl
size="medium"
className="recommended-addons-modal__sort-select"
sx={{ minWidth: 180 }}
>
<InputLabel
id="recommended-addons-sort-label"
sx={{ fontSize: '1rem' }}
>
Sort by Price
</InputLabel>
<Select
labelId="recommended-addons-sort-label"
value={sortOrder}
label="Sort by Price"
onChange={handleSortChange}
sx={{ fontSize: '1rem' }}
>
<MenuItem value="" sx={{ fontSize: '1rem' }}>
<em>None</em>
</MenuItem>
<MenuItem value="asc" sx={{ fontSize: '1rem' }}>
<Box display="flex" alignItems="center">
<ArrowUpIcon fontSize="small" />
<Typography sx={{ ml: 0.5, fontSize: '1rem' }}>
Low to High
</Typography>
</Box>
</MenuItem>
<MenuItem value="desc" sx={{ fontSize: '1rem' }}>
<Box display="flex" alignItems="center">
<ArrowDownIcon fontSize="small" />
<Typography sx={{ ml: 0.5, fontSize: '1rem' }}>
High to Low
</Typography>
</Box>
</MenuItem>
</Select>
</FormControl>
</Box>

{filteredAndSortedItems.length === 0 ? (
<Box className="recommended-addons-modal__empty-state">
<Typography variant="body1">
No items match your search criteria.
</Typography>
</Box>
) : (
<Box className="recommended-addons-modal__list">
<Box className="recommended-addons-modal__list-header">
<Typography
variant="subtitle2"
className="recommended-addons-modal__header-name"
>
Product Name
</Typography>
<Typography
variant="subtitle2"
className="recommended-addons-modal__header-provider"
>
Provider
</Typography>
<Typography
variant="subtitle2"
className="recommended-addons-modal__header-price"
>
Price
</Typography>
<Typography
variant="subtitle2"
className="recommended-addons-modal__header-action"
>
Action
</Typography>
</Box>
{filteredAndSortedItems.map((item) => (
<RecommendedItemsCard
key={item.id}
recommendedItem={item}
/>
)}
</Grid>
))}
</Grid>
))}
</Box>
)}
</>
)}
</DialogContent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,19 @@

import { clsx, PlusIcon } from '@finos/legend-art';
import type { TerminalResult } from '@finos/legend-server-marketplace';
import {
Box,
Button,
Card,
CardActions,
CardContent,
Chip,
CircularProgress,
Typography,
} from '@mui/material';
import { Box, Button, CircularProgress, Typography } from '@mui/material';
import { flowResult } from 'mobx';
import { useState } from 'react';
import { assertErrorThrown } from '@finos/legend-shared';
import { toastManager } from '../Toast/CartToast.js';
import { useLegendMarketplaceBaseStore } from '../../application/providers/LegendMarketplaceFrameworkProvider.js';

interface RecommendedItemsCardProps {
vendorProfileId?: number;
recommendedItem: TerminalResult;
}

export const RecommendedItemsCard = (props: RecommendedItemsCardProps) => {
const { vendorProfileId, recommendedItem } = props;
const { recommendedItem } = props;
const legendMarketplaceBaseStore = useLegendMarketplaceBaseStore();
const [isAddingToCart, setIsAddingToCart] = useState(false);
const [inCart, setInCart] = useState(false);
Expand All @@ -47,9 +37,6 @@ export const RecommendedItemsCard = (props: RecommendedItemsCardProps) => {
setIsAddingToCart(true);
const cartItemRequest =
legendMarketplaceBaseStore.cartStore.providerToCartRequest(addon);
if (vendorProfileId) {
cartItemRequest.vendorProfileId = vendorProfileId;
}

flowResult(
legendMarketplaceBaseStore.cartStore.addToCartWithAPI(cartItemRequest),
Expand All @@ -70,62 +57,45 @@ export const RecommendedItemsCard = (props: RecommendedItemsCardProps) => {
};

return (
<Card className="recommended-addons-modal__addon-card">
<CardContent className="recommended-addons-modal__card-content">
<Box className="recommended-addons-modal__card-header">
<Typography
variant="subtitle1"
className="recommended-addons-modal__product-name"
>
{recommendedItem.productName}
</Typography>
<Chip
label={recommendedItem.providerName}
size="small"
className="recommended-addons-modal__provider-chip"
/>
</Box>

<Typography
variant="body2"
className="recommended-addons-modal__description"
>
{recommendedItem.description || 'No description available'}
</Typography>

<Box className="recommended-addons-modal__card-footer">
<Typography
variant="body2"
className={clsx('recommended-addons-modal__price', {
'recommended-addons-modal__price__free':
recommendedItem.price === 0,
<Box className="recommended-addons-modal__list-item">
<Typography
variant="body1"
className="recommended-addons-modal__item-name"
>
{recommendedItem.productName}
</Typography>
<Typography
variant="body2"
className="recommended-addons-modal__item-provider"
>
{recommendedItem.providerName}
</Typography>
<Typography
variant="body2"
className="recommended-addons-modal__item-price"
>
{recommendedItem.price === 0
? 'Free'
: recommendedItem.price.toLocaleString('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 2,
})}
>
{recommendedItem.price === 0
? 'Free'
: recommendedItem.price.toLocaleString('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 2,
})}
</Typography>
</Box>
</CardContent>

<CardActions className="recommended-addons-modal__card-actions">
</Typography>
<Box className="recommended-addons-modal__item-action">
<Button
variant={inCart ? 'outlined' : 'contained'}
onClick={() => handleAddAddonToCart(recommendedItem)}
disabled={inCart || isAddingToCart}
fullWidth={true}
size="small"
className={clsx('recommended-addons-modal__add-btn', {
'recommended-addons-modal__add-btn__added': inCart,
'recommended-addons-modal__add-btn--added': inCart,
})}
>
{isAddingToCart ? (
<>
Adding... &nbsp;
<CircularProgress size={16} />
<CircularProgress size={14} />
</>
) : inCart ? (
'Added to Cart'
Expand All @@ -136,7 +106,7 @@ export const RecommendedItemsCard = (props: RecommendedItemsCardProps) => {
</>
)}
</Button>
</CardActions>
</Card>
</Box>
</Box>
);
};
Loading