/**
* 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 { type JSX, useCallback, useEffect, useState } from 'react';
import { LegendMarketplaceSearchBar } from '../../components/SearchBar/LegendMarketplaceSearchBar.js';
import {
Button,
IconButton,
Tooltip,
Typography,
List,
ListItem,
CircularProgress,
Collapse,
} from '@mui/material';
import type {
TerminalResult,
TraderProfile,
} from '@finos/legend-server-marketplace';
import { LegendMarketplaceTerminalCard } from '../../components/ProviderCard/LegendMarketplaceTerminalCard.js';
import { LegendMarketplaceOrderProfileCard } from '../../components/ProviderCard/LegendMarketplaceOrderProfileCard.js';
import { LegendMarketplaceOwnedTerminalCard } from '../../components/ProviderCard/LegendMarketplaceOwnedTerminalCard.js';
import {
type LegendMarketPlaceVendorDataStore,
VendorDataProviderType,
} from '../../stores/LegendMarketPlaceVendorDataStore.js';
import { LegendMarketplacePage } from '../LegendMarketplacePage.js';
import {
useLegendMarketPlaceVendorDataStore,
withLegendMarketplaceVendorDataStore,
} from '../../application/providers/LegendMarketplaceVendorDataProvider.js';
import { useParams } from '@finos/legend-application/browser';
import {
CaretRightIcon,
ChevronDownIcon,
ChevronRightIcon,
InfoCircleIcon,
UserSearchInput,
} from '@finos/legend-art';
import { flowResult } from 'mobx';
import { type LegendUser } from '@finos/legend-shared';
import { useLegendMarketplaceBaseStore } from '../../application/providers/LegendMarketplaceFrameworkProvider.js';
import { PaginationControls } from '../../components/Pagination/PaginationControls.js';
import { UserRenderer } from '@finos/legend-extension-dsl-data-product';
import { LegendMarketplaceOptionSelector } from '../../components/OptionSelector/LegendMarketplaceOptionSelector.js';
import {
LegendMarketplaceTelemetryHelper,
TERMINAL_SEARCH_LOCATION,
} from '../../__lib__/LegendMarketplaceTelemetryHelper.js';
export const RefinedVendorRadioSelector = observer(
(props: { vendorDataState: LegendMarketPlaceVendorDataStore }) => {
const { vendorDataState } = props;
const radioOptions = [
VendorDataProviderType.ALL,
VendorDataProviderType.TERMINAL_LICENSE,
VendorDataProviderType.ADD_ONS,
VendorDataProviderType.ORDER_PROFILE,
];
const onRadioChange = useCallback(
(value: VendorDataProviderType) => {
LegendMarketplaceTelemetryHelper.logEvent_TerminalsAddonsFilterTab(
vendorDataState.applicationStore.telemetryService,
value,
);
vendorDataState.setProviderDisplayState(value);
flowResult(vendorDataState.populateProviders()).catch(
vendorDataState.applicationStore.alertUnhandledError,
);
},
[vendorDataState],
);
return (
);
},
);
/**
* Shared section wrapper that renders the header (title, count badge, tooltip,
* "View more" button) plus any card content passed via the `renderCards` prop.
* Use this as the base for both terminal/add-on and order-profile sections so
* the header logic lives in exactly one place.
*/
const SearchResultsSection = observer(
(props: {
vendorDataState: LegendMarketPlaceVendorDataStore;
sectionTitle: VendorDataProviderType;
itemCount: number;
totalCount: number | undefined;
tooltip: string | undefined;
seeAll: boolean | undefined;
renderCards: () => JSX.Element;
}): JSX.Element => {
const {
vendorDataState,
sectionTitle,
itemCount,
totalCount,
tooltip,
seeAll,
renderCards,
} = props;
const showCount = vendorDataState.searchTerm.trim().length > 0;
// A 200 response with an empty section (no results) means there is
// nothing more to page into, so "View more" should be disabled rather
// than navigating to an empty tab.
const hasNoResults = (totalCount ?? itemCount) === 0;
return (
);
},
);
export const LegendMarketplaceVendorData = withLegendMarketplaceVendorDataStore(
observer(() => {
const marketPlaceVendorDataStore = useLegendMarketPlaceVendorDataStore();
const marketplaceStore = useLegendMarketplaceBaseStore();
const cartStore = marketplaceStore.cartStore;
const currentUserId =
marketplaceStore.applicationStore.identityService.currentUser;
const handleSearch = useCallback(
(query: string | undefined) => {
LegendMarketplaceTelemetryHelper.logEvent_TerminalsAddonsSearch(
marketplaceStore.applicationStore.telemetryService,
query ?? '',
TERMINAL_SEARCH_LOCATION.MAIN_CATALOG,
marketPlaceVendorDataStore.providerDisplayState,
marketPlaceVendorDataStore.selectedUser.id !== currentUserId,
);
marketPlaceVendorDataStore.setSearchTerm(query ?? '');
// A search should surface matches across all provider types, so
// always land on the ALL tab rather than staying on whichever tab
// happened to be selected before the search was made.
if ((query ?? '').trim().length > 0) {
marketPlaceVendorDataStore.setProviderDisplayState(
VendorDataProviderType.ALL,
);
}
flowResult(marketPlaceVendorDataStore.populateProviders()).catch(
marketPlaceVendorDataStore.applicationStore.alertUnhandledError,
);
},
[marketPlaceVendorDataStore, marketplaceStore, currentUserId],
);
const handleSearchChange = useCallback(
(query: string) => {
if (query === '') {
marketPlaceVendorDataStore.setSearchTerm('');
// Clearing the search (e.g. via the search bar's "x" button) should
// bring the user back to the ALL tab, matching the state they'd be
// in before ever searching.
marketPlaceVendorDataStore.setProviderDisplayState(
VendorDataProviderType.ALL,
);
flowResult(marketPlaceVendorDataStore.populateProviders()).catch(
marketPlaceVendorDataStore.applicationStore.alertUnhandledError,
);
}
},
[marketPlaceVendorDataStore],
);
useEffect(() => {
marketPlaceVendorDataStore.init();
LegendMarketplaceTelemetryHelper.logEvent_ViewTerminalsAddonsPage(
marketplaceStore.applicationStore.telemetryService,
marketPlaceVendorDataStore.selectedUser.id !== currentUserId,
);
}, [marketPlaceVendorDataStore, marketplaceStore, currentUserId]);
return (