Marketplace.constants.tsx167 lines · main
| 1 | import { |
| 2 | BadgeCheck, |
| 3 | BarChart3, |
| 4 | Boxes, |
| 5 | Cable, |
| 6 | Cpu, |
| 7 | CreditCard, |
| 8 | Database, |
| 9 | Fingerprint, |
| 10 | KeyRound, |
| 11 | Mail, |
| 12 | MessageSquare, |
| 13 | MousePointerClick, |
| 14 | Package2, |
| 15 | Plug, |
| 16 | Server, |
| 17 | ShieldCheck, |
| 18 | Users, |
| 19 | Webhook, |
| 20 | Wrench, |
| 21 | Zap, |
| 22 | type LucideIcon, |
| 23 | } from 'lucide-react' |
| 24 | import type { ReactNode } from 'react' |
| 25 | import { Badge, cn, IconPartners } from 'ui' |
| 26 | |
| 27 | import type { |
| 28 | IntegrationDefinition, |
| 29 | MarketplaceSource, |
| 30 | } from '@/components/interfaces/Integrations/Landing/Integrations.constants' |
| 31 | |
| 32 | export type { MarketplaceSource } from '@/components/interfaces/Integrations/Landing/Integrations.constants' |
| 33 | |
| 34 | // Launch-partner pin list for the featured rail. Order here is the order |
| 35 | // shown. Stripe is the only first-party template in the set (id matches the |
| 36 | // static catalogue); the rest are marketplaceDB slugs and only render if the |
| 37 | // listing is published. |
| 38 | export const FEATURED_INTEGRATION_IDS = [ |
| 39 | 'grafana', |
| 40 | 'stripe_sync_engine', |
| 41 | 'aikido', |
| 42 | 'aikido-security', |
| 43 | 'doppler', |
| 44 | 'cipherstash', |
| 45 | ] as const |
| 46 | |
| 47 | export type MarketplaceIntegrationType = 'oauth' | 'postgres_extension' | 'template' | 'wrapper' |
| 48 | |
| 49 | export const INTEGRATION_TYPES: Array<{ |
| 50 | key: MarketplaceIntegrationType |
| 51 | label: string |
| 52 | icon: LucideIcon |
| 53 | }> = [ |
| 54 | { key: 'oauth', label: 'OAuth App', icon: Plug }, |
| 55 | { key: 'postgres_extension', label: 'Postgres Module', icon: Database }, |
| 56 | { key: 'template', label: 'Template', icon: Package2 }, |
| 57 | { key: 'wrapper', label: 'Wrapper', icon: Cable }, |
| 58 | ] |
| 59 | |
| 60 | // Lucide icon per marketplace category slug. New categories fall back to a |
| 61 | // neutral icon (Boxes) so nothing breaks when the marketplaceDB is updated. |
| 62 | export const CATEGORY_ICONS: Record<string, LucideIcon> = { |
| 63 | observability: BarChart3, |
| 64 | security: ShieldCheck, |
| 65 | billing: CreditCard, |
| 66 | secrets: KeyRound, |
| 67 | email: Mail, |
| 68 | wrappers: Database, |
| 69 | ai: Cpu, |
| 70 | ai_vectors: Cpu, |
| 71 | storage: Package2, |
| 72 | postgres_extension: Database, |
| 73 | wrapper: Cable, |
| 74 | devtools: Wrench, |
| 75 | auth: Fingerprint, |
| 76 | 'low-code': MousePointerClick, |
| 77 | 'data-platform': Server, |
| 78 | api: Webhook, |
| 79 | 'caching-offline-first': Zap, |
| 80 | messaging: MessageSquare, |
| 81 | } |
| 82 | |
| 83 | export const FEATURED_CATEGORIES: Array<{ slug: string; name: string }> = [ |
| 84 | { slug: 'observability', name: 'Observability' }, |
| 85 | { slug: 'security', name: 'Security' }, |
| 86 | { slug: 'billing', name: 'Billing' }, |
| 87 | { slug: 'devtools', name: 'DevTools' }, |
| 88 | { slug: 'ai_vectors', name: 'AI & Vectors' }, |
| 89 | { slug: 'storage', name: 'Storage' }, |
| 90 | ] |
| 91 | |
| 92 | // Categories excluded from the filter dropdown — they either overlap with |
| 93 | // dedicated filter groups (type/source) or are unused in the current catalogue. |
| 94 | export const EXCLUDED_CATEGORY_SLUGS = new Set([ |
| 95 | 'agencies', |
| 96 | 'foreign-data-wrapper', |
| 97 | 'app-templates', |
| 98 | ]) |
| 99 | |
| 100 | export const getCategoryIcon = (slug: string | null | undefined): LucideIcon => { |
| 101 | if (!slug) return Boxes |
| 102 | return CATEGORY_ICONS[slug] ?? Boxes |
| 103 | } |
| 104 | |
| 105 | export const formatCategoryLabel = ( |
| 106 | slug: string | null | undefined, |
| 107 | categoryOptions?: Array<{ slug: string; name: string }> |
| 108 | ): string => { |
| 109 | if (!slug) return '' |
| 110 | const pinned = FEATURED_CATEGORIES.find((c) => c.slug === slug) |
| 111 | if (pinned) return pinned.name |
| 112 | const remote = categoryOptions?.find((c) => c.slug === slug) |
| 113 | if (remote) return remote.name |
| 114 | return slug |
| 115 | .split(/[-_]/) |
| 116 | .filter(Boolean) |
| 117 | .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) |
| 118 | .join(' ') |
| 119 | } |
| 120 | |
| 121 | export const getMarketplaceType = ( |
| 122 | integration: IntegrationDefinition |
| 123 | ): MarketplaceIntegrationType => { |
| 124 | if (integration.type === 'wrapper') return 'wrapper' |
| 125 | if (integration.type === 'postgres_extension') return 'postgres_extension' |
| 126 | if (integration.id === 'stripe_sync_engine') return 'template' |
| 127 | return 'oauth' |
| 128 | } |
| 129 | |
| 130 | export const getMarketplaceTypeLabel = (type: MarketplaceIntegrationType): string => |
| 131 | INTEGRATION_TYPES.find((t) => t.key === type)?.label ?? type |
| 132 | |
| 133 | export const MARKETPLACE_SOURCES: Array<{ |
| 134 | key: MarketplaceSource |
| 135 | label: string |
| 136 | icon: LucideIcon | ((props: { className?: string }) => ReactNode) |
| 137 | }> = [ |
| 138 | { key: 'Official', label: 'Official', icon: BadgeCheck }, |
| 139 | { key: 'Partner', label: 'Partner', icon: IconPartners }, |
| 140 | { key: 'Community', label: 'Community', icon: Users }, |
| 141 | ] |
| 142 | |
| 143 | export const getMarketplaceSource = (integration: IntegrationDefinition): MarketplaceSource => { |
| 144 | if (integration.source) return integration.source |
| 145 | return integration.listingId ? 'Partner' : 'Official' |
| 146 | } |
| 147 | |
| 148 | export const MarketplaceSourceBadge = ({ |
| 149 | source, |
| 150 | className, |
| 151 | }: { |
| 152 | source: MarketplaceSource |
| 153 | className?: string |
| 154 | }) => { |
| 155 | switch (source) { |
| 156 | case 'Partner': |
| 157 | return ( |
| 158 | <Badge className={cn('border-foreground-lighter/50', className)}> |
| 159 | <IconPartners size={10} /> Partner |
| 160 | </Badge> |
| 161 | ) |
| 162 | case 'Community': |
| 163 | return <Badge className={className}>Community</Badge> |
| 164 | case 'Official': |
| 165 | return <Badge className={className}>Official</Badge> |
| 166 | } |
| 167 | } |