Marketplace.constants.tsx 4.8 KB

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