| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- // @ts-nocheck
- import { useQuery } from '@tanstack/react-query'
- import { parseAsString, parseAsStringEnum, useQueryState } from 'nuqs'
- import { useMemo } from 'react'
- import { Button, Card, ShadowScrollArea, Table, TableBody, TableHeader } from 'ui'
- import {
- EmptyStatePresentational,
- PageContainer,
- PageHeader,
- PageHeaderDescription,
- PageHeaderMeta,
- PageHeaderSummary,
- PageHeaderTitle,
- } from 'ui-patterns'
- import {
- EXCLUDED_CATEGORY_SLUGS,
- FEATURED_INTEGRATION_IDS,
- formatCategoryLabel,
- getMarketplaceSource,
- getMarketplaceType,
- INTEGRATION_TYPES,
- MARKETPLACE_SOURCES,
- type MarketplaceIntegrationType,
- type MarketplaceSource,
- } from './Marketplace.constants'
- import { MarketplaceCard } from './MarketplaceCard'
- import { MarketplaceFeaturedHero } from './MarketplaceFeaturedHero'
- import { MarketplaceFilterBar, type ViewMode } from './MarketplaceFilterBar'
- import { MarketplaceListHeader, MarketplaceListRow } from './MarketplaceListRow'
- import { IntegrationLoadingCard } from '@/components/interfaces/Integrations/Landing/IntegrationCard'
- import { useAvailableIntegrations } from '@/components/interfaces/Integrations/Landing/useAvailableIntegrations'
- import { useInstalledIntegrations } from '@/components/interfaces/Integrations/Landing/useInstalledIntegrations'
- import { AlertError } from '@/components/ui/AlertError'
- import { DocsButton } from '@/components/ui/DocsButton'
- import { useMarketplaceCategoriesQuery } from '@/data/marketplace/integration-categories-query'
- import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
- import { DOCS_URL } from '@/lib/constants'
- const MARKETPLACE_VIEW_MODE_STORAGE_KEY = 'briven.marketplace.viewMode'
- export const MarketplaceIndex = () => {
- const [search, setSearch] = useQueryState(
- 'search',
- parseAsString.withDefault('').withOptions({ clearOnDefault: true })
- )
- const [category, setCategory] = useQueryState(
- 'category',
- parseAsString.withOptions({ clearOnDefault: true })
- )
- const [type, setType] = useQueryState(
- 'type',
- parseAsStringEnum<MarketplaceIntegrationType>([
- 'oauth',
- 'postgres_extension',
- 'template',
- 'wrapper',
- ]).withOptions({ clearOnDefault: true })
- )
- const [source, setSource] = useQueryState(
- 'source',
- parseAsStringEnum<MarketplaceSource>(['Official', 'Partner', 'Community']).withOptions({
- clearOnDefault: true,
- })
- )
- const [viewMode, setViewMode] = useLocalStorageQuery<ViewMode>(
- MARKETPLACE_VIEW_MODE_STORAGE_KEY,
- 'grid'
- )
- const {
- data: availableIntegrations,
- error,
- isPending: isLoadingAvailable,
- isError,
- isSuccess: isSuccessAvailable,
- } = useAvailableIntegrations()
- const {
- installedIntegrations,
- isLoading: isLoadingInstalled,
- isSuccess: isSuccessInstalled,
- } = useInstalledIntegrations()
- const installedIds = installedIntegrations.map((i) => i.id)
- const isLoading = isLoadingAvailable || isLoadingInstalled
- const isSuccess = isSuccessAvailable && isSuccessInstalled
- const { data: marketplaceCategories = [] } = useQuery(useMarketplaceCategoriesQuery())
- const categoryOptions = useMemo(
- () =>
- marketplaceCategories
- .filter(
- (c): c is { slug: string; name: string } & typeof c =>
- !!c.slug && !!c.name && !EXCLUDED_CATEGORY_SLUGS.has(c.slug)
- )
- .map((c) => ({ slug: c.slug, name: c.name })),
- [marketplaceCategories]
- )
- const typeCounts = useMemo(() => {
- const all = availableIntegrations ?? []
- return Object.fromEntries(
- INTEGRATION_TYPES.map(({ key }) => [
- key,
- all.filter((i) => getMarketplaceType(i) === key).length,
- ])
- ) as Record<MarketplaceIntegrationType, number>
- }, [availableIntegrations])
- const sourceCounts = useMemo(() => {
- const all = availableIntegrations ?? []
- return Object.fromEntries(
- MARKETPLACE_SOURCES.map(({ key }) => [
- key,
- all.filter((i) => getMarketplaceSource(i) === key).length,
- ])
- ) as Record<MarketplaceSource, number>
- }, [availableIntegrations])
- const categoryCounts = useMemo(() => {
- const all = availableIntegrations ?? []
- const counts: Record<string, number> = {}
- for (const { slug } of categoryOptions) {
- counts[slug] = all.filter((i) => i.categories?.includes(slug)).length
- }
- return counts
- }, [availableIntegrations, categoryOptions])
- const hasActiveFilter = !!(category || type || source)
- const hasSearchOrFilter = hasActiveFilter || search.length > 0
- const filtered = useMemo(() => {
- let result = availableIntegrations ?? []
- if (category) {
- result = result.filter((i) => i.categories?.includes(category))
- }
- if (type) {
- result = result.filter((i) => getMarketplaceType(i) === type)
- }
- if (source) {
- result = result.filter((i) => getMarketplaceSource(i) === source)
- }
- if (search.length > 0) {
- const needle = search.toLowerCase()
- result = result.filter((i) => i.name.toLowerCase().includes(needle))
- }
- return [...result].sort((a, b) => {
- const aInstalled = installedIds.includes(a.id)
- const bInstalled = installedIds.includes(b.id)
- if (aInstalled && !bInstalled) return -1
- if (!aInstalled && bInstalled) return 1
- return a.name.localeCompare(b.name)
- })
- }, [availableIntegrations, category, type, source, search, installedIds])
- const featured = useMemo(() => {
- if (hasSearchOrFilter) return []
- const byId = new Map((availableIntegrations ?? []).map((i) => [i.id, i]))
- return FEATURED_INTEGRATION_IDS.map((id) => byId.get(id)).filter(
- (i): i is NonNullable<typeof i> => !!i
- )
- }, [availableIntegrations, hasSearchOrFilter])
- const clearAll = () => {
- setCategory(null)
- setType(null)
- setSource(null)
- setSearch('')
- }
- const activeFilters = [category, type, source].filter(Boolean)
- const pageTitle = useMemo(() => {
- if (activeFilters.length !== 1) return 'Integrations Marketplace'
- if (category)
- return `Integrations Marketplace: ${formatCategoryLabel(category, categoryOptions)}`
- if (type)
- return `Integrations Marketplace: ${INTEGRATION_TYPES.find((t) => t.key === type)?.label ?? type}s`
- if (source)
- return `Integrations Marketplace: ${MARKETPLACE_SOURCES.find((s) => s.key === source)?.label ?? source}`
- return 'Integrations Marketplace'
- }, [activeFilters.length, category, type, source, categoryOptions])
- return (
- <>
- <PageHeader size="large">
- <PageHeaderMeta>
- <PageHeaderSummary>
- <PageHeaderTitle>{pageTitle}</PageHeaderTitle>
- <PageHeaderDescription>
- Explore native and third-party integrations to add functionality to your Briven
- project.
- </PageHeaderDescription>
- </PageHeaderSummary>
- <div className="flex shrink-0 items-center gap-2">
- <DocsButton href={`${DOCS_URL}/guides/integrations/briven-marketplace`} />
- </div>
- </PageHeaderMeta>
- </PageHeader>
- <PageContainer size="large" className="flex flex-col gap-4 py-6">
- {isLoading && (
- <div className="grid gap-4 xl:grid-cols-3 2xl:grid-cols-4">
- {Array.from({ length: 8 }).map((_, idx) => (
- <IntegrationLoadingCard key={`marketplace-loading-${idx}`} />
- ))}
- </div>
- )}
- {isError && (
- <AlertError subject="Failed to retrieve available integrations" error={error} />
- )}
- {isSuccess && (
- <>
- {featured.length > 0 && (
- <MarketplaceFeaturedHero
- integrations={featured}
- installedIds={installedIds}
- categoryOptions={categoryOptions}
- />
- )}
- <MarketplaceFilterBar
- resultCount={filtered.length}
- search={search}
- onSearchChange={(v) => setSearch(v)}
- category={category}
- onCategoryChange={(v) => setCategory(v)}
- categoryOptions={categoryOptions}
- categoryCounts={categoryCounts}
- type={type}
- onTypeChange={(v) => setType(v)}
- typeCounts={typeCounts}
- source={source}
- onSourceChange={(v) => setSource(v)}
- sourceCounts={sourceCounts}
- viewMode={viewMode}
- onViewModeChange={(v) => setViewMode(v)}
- hasActiveFilter={hasActiveFilter}
- onClearFilters={clearAll}
- />
- {filtered.length === 0 && (
- <EmptyStatePresentational title="No results found">
- <Button type="default" onClick={clearAll}>
- Clear filters
- </Button>
- </EmptyStatePresentational>
- )}
- {filtered.length > 0 &&
- (viewMode === 'grid' ? (
- <div className="grid @lg:grid-cols-2 gap-3 @4xl:grid-cols-3">
- {filtered.map((integration) => (
- <MarketplaceCard
- key={integration.id}
- integration={integration}
- isInstalled={installedIds.includes(integration.id)}
- />
- ))}
- </div>
- ) : (
- <Card>
- <ShadowScrollArea>
- <Table>
- <TableHeader>
- <MarketplaceListHeader />
- </TableHeader>
- <TableBody>
- {filtered.map((integration) => (
- <MarketplaceListRow
- key={integration.id}
- integration={integration}
- isInstalled={installedIds.includes(integration.id)}
- />
- ))}
- </TableBody>
- </Table>
- </ShadowScrollArea>
- </Card>
- ))}
- </>
- )}
- </PageContainer>
- </>
- )
- }
|