/**
* Copyright (c) 2025-present, Goldman Sachs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { observer } from 'mobx-react-lite';
import { useCallback, useEffect, useRef, useState } from 'react';
import { flowResult } from 'mobx';
import {
Drawer,
Box,
IconButton,
Typography,
Divider,
Select,
MenuItem,
FormControl,
InputLabel,
Button,
ButtonGroup,
Chip,
CircularProgress,
Tooltip,
} from '@mui/material';
import {
CloseIcon,
TrashIcon,
ChevronDownIcon,
ChevronRightIcon,
InfoCircleIcon,
clsx,
} from '@finos/legend-art';
import { useLegendMarketplaceBaseStore } from '../../application/providers/LegendMarketplaceFrameworkProvider.js';
import {
type CartVendorGroup,
CartStore,
} from '../../stores/cart/CartStore.js';
import { useApplicationStore } from '@finos/legend-application';
import { type CartItem } from '@finos/legend-server-marketplace';
import {
formatCardPrice,
formatItemPrice,
} from '../ProviderCard/orderProfileUtils.js';
// ─── Helpers ─────────────────────────────────────────────────────────────────
const MANDATORY_ADDON_TOOLTIP =
'This is a mandatory add-on included with the vendor profile.';
// ─── Cart Item Card (Add-on) ─────────────────────────────────────────────────
const CartAddonCard = (props: {
item: CartItem;
vendorGroup: CartItem[];
isLastAddon: boolean;
onDelete: (item: CartItem, vendorGroup: CartItem[]) => void;
disabled: boolean;
}): React.ReactNode => {
const { item, vendorGroup, isLastAddon, onDelete, disabled } = props;
const removeButton = (
onDelete(item, vendorGroup)}
className="legend-marketplace-cart-drawer__addon-card__remove-btn"
disabled={disabled}
aria-label={`Remove ${item.productName}`}
>
);
return (
{item.productName}
{item.isMandatory && (
)}
{item.isMandatory ? (
{removeButton}
) : (
removeButton
)}
{formatItemPrice(item.price)}
/month
);
};
// ─── Cart Summary Bar ────────────────────────────────────────────────────────
const CartSummaryBar = (props: { formattedTotal: string }): React.ReactNode => {
const { formattedTotal } = props;
return (
Monthly Total
{formattedTotal}
);
};
// ─── Vendor Group Header (Parent Card) ───────────────────────────────────────
const CartVendorGroupHeader = (props: {
parentItem: CartItem | undefined;
displayParent: CartVendorGroup['displayParent'];
vendorGroup: CartItem[];
addons: CartItem[];
addonTotalPrice: number;
addonLabel: string;
isExpanded: boolean;
isSynthetic: boolean;
onToggle: () => void;
onDelete: (item: CartItem, vendorGroup: CartItem[]) => void;
onDeleteSyntheticGroup: (vendorGroup: CartItem[]) => void;
disabled: boolean;
}): React.ReactNode => {
const {
parentItem,
displayParent,
vendorGroup,
addons,
addonTotalPrice,
addonLabel,
isExpanded,
isSynthetic,
onToggle,
onDelete,
onDeleteSyntheticGroup,
disabled,
} = props;
return (
{displayParent.providerName}
{displayParent.productName}
onDeleteSyntheticGroup(vendorGroup)
: () => {
if (parentItem) {
onDelete(parentItem, vendorGroup);
}
}
}
className="legend-marketplace-cart-drawer__item-card__remove-btn"
disabled={disabled}
aria-label={
isSynthetic
? `Remove all items under ${displayParent.productName}`
: `Remove ${displayParent.productName}`
}
>
{isSynthetic ? (
Already Subscribed
) : (
{formatItemPrice(displayParent.monthlyPrice ?? 0)}
/month
)}
{addons.length > 0 && (
{
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onToggle();
}
}}
aria-expanded={isExpanded}
aria-label={`${isExpanded ? 'Collapse' : 'Expand'} add-ons for ${displayParent.productName}`}
>
{isExpanded ? : }
{addonLabel}
{!isExpanded && ` – ${formatCardPrice(addonTotalPrice)}`}
)}
);
};
// ─── Main CartDrawer ─────────────────────────────────────────────────────────
export const CartDrawer = observer((): React.ReactNode => {
const baseStore = useLegendMarketplaceBaseStore();
const applicationStore = useApplicationStore();
const cart = baseStore.cartStore;
// Track which vendor groups are expanded (all expanded by default)
const [expandedVendors, setExpandedVendors] = useState>(
new Set(),
);
const knownVendorIdsRef = useRef>(new Set());
// Refresh cart when drawer opens
useEffect(() => {
if (cart.open) {
flowResult(cart.refresh()).catch((error) => {
baseStore.applicationStore.notificationService.notifyError(
`Failed to refresh cart: ${error}`,
);
});
}
}, [cart, cart.open, baseStore.applicationStore]);
// Initialize all vendor groups as expanded when items change
useEffect(() => {
const currentVendorIds = new Set(cart.vendorGroupIds);
setExpandedVendors((previousExpanded) => {
const nextExpanded = new Set();
for (const vpId of previousExpanded) {
if (currentVendorIds.has(vpId)) {
nextExpanded.add(vpId);
}
}
for (const vpId of currentVendorIds) {
if (!knownVendorIdsRef.current.has(vpId)) {
nextExpanded.add(vpId);
}
}
return nextExpanded;
});
knownVendorIdsRef.current = currentVendorIds;
}, [cart.vendorGroupIds]);
const toggleVendor = useCallback((vpId: number) => {
setExpandedVendors((prev) => {
const next = new Set(prev);
if (next.has(vpId)) {
next.delete(vpId);
} else {
next.add(vpId);
}
return next;
});
}, []);
const collapseAll = useCallback(() => {
setExpandedVendors(new Set());
}, []);
const expandAll = useCallback(() => {
setExpandedVendors(new Set(cart.vendorGroupIds));
}, [cart.vendorGroupIds]);
const vendorGroups = cart.vendorGroups;
const handleDeleteItem = useCallback(
(item: CartItem, vendorGroup: CartItem[]) => {
cart.requestDeleteItemConfirmation(item, vendorGroup);
},
[cart],
);
const handleDeleteSyntheticGroup = useCallback(
(vendorGroup: CartItem[]) => {
cart.requestDeleteGroupConfirmation(vendorGroup);
},
[cart],
);
const vendorGroupCount = cart.vendorGroupIds.length;
return (
cart.setOpen(false)}
slotProps={{
paper: {
className: 'legend-marketplace-cart-drawer',
sx: {
width: { xs: '100vw', sm: '400px' },
maxWidth: '90vw',
marginTop: 'var(--legend-marketplace-header-height)',
height: 'calc(100% - var(--legend-marketplace-header-height))',
},
},
}}
>
Cart ({cart.cartSummary.total_items})
cart.setOpen(false)}
size="medium"
aria-label="Close cart"
className="legend-marketplace-cart-drawer__close-btn"
>
{/* Collapse / Expand All controls */}
{!cart.loadingState.isInProgress &&
cart.cartSummary.total_items > 0 &&
vendorGroupCount > 1 && (
)}
{cart.loadingState.isInProgress && (
Loading cart...
)}
{!cart.loadingState.isInProgress &&
cart.cartSummary.total_items <= 0 && (
Your cart is empty
)}
{!cart.loadingState.isInProgress &&
cart.cartSummary.total_items > 0 && (
{vendorGroups.map(
({
vpId,
parentItem,
displayParent,
addons,
groupItems,
isSynthetic,
addonTotalPrice,
addonLabel,
}) => (
toggleVendor(vpId)}
onDelete={handleDeleteItem}
onDeleteSyntheticGroup={handleDeleteSyntheticGroup}
disabled={cart.loadingState.isInProgress}
/>
{expandedVendors.has(vpId) &&
addons.map((addon, index) => (
))}
),
)}
)}
{!cart.loadingState.isInProgress && cart.cartSummary.total_items > 0 && (
)}
{'Please Choose a Business Reason'}
*
Select a Reason
{cart.cartSummary.total_items > 0 && !cart.businessReason && (
Select a business reason to continue.
)}
);
});