MarketplaceSidebar.tsx119 lines · main
| 1 | import { useParams } from 'common' |
| 2 | import { ArrowUpRight, BookOpen, LayoutGrid, PlusSquare } from 'lucide-react' |
| 3 | import Link from 'next/link' |
| 4 | import { useRouter } from 'next/router' |
| 5 | import { cn } from 'ui' |
| 6 | |
| 7 | import { useInstalledIntegrations } from '@/components/interfaces/Integrations/Landing/useInstalledIntegrations' |
| 8 | import { DOCS_URL } from '@/lib/constants' |
| 9 | |
| 10 | const sectionLabelCls = |
| 11 | 'px-2 pt-4 pb-1.5 font-mono text-xs uppercase tracking-wider text-foreground-lighter' |
| 12 | |
| 13 | interface SidebarLinkProps { |
| 14 | href: string |
| 15 | active?: boolean |
| 16 | icon?: React.ReactNode |
| 17 | label: React.ReactNode |
| 18 | count?: number | string |
| 19 | className?: string |
| 20 | } |
| 21 | |
| 22 | const SidebarLink = ({ href, active, icon, label, count, className }: SidebarLinkProps) => ( |
| 23 | <Link |
| 24 | href={href} |
| 25 | className={cn( |
| 26 | 'flex items-center justify-between rounded px-2 py-1 text-sm', |
| 27 | 'text-foreground-light hover:bg-surface-200 hover:text-foreground', |
| 28 | active && 'bg-surface-200 text-foreground', |
| 29 | className |
| 30 | )} |
| 31 | > |
| 32 | <span className="flex min-w-0 items-center gap-2"> |
| 33 | <span className="text-foreground-lighter">{icon}</span> |
| 34 | {label} |
| 35 | </span> |
| 36 | {count !== undefined && ( |
| 37 | <span className="font-mono text-xs text-foreground-lighter">{count}</span> |
| 38 | )} |
| 39 | </Link> |
| 40 | ) |
| 41 | |
| 42 | const HELP_LINKS: Array<{ icon: React.ReactNode; label: string; href: string }> = [ |
| 43 | { |
| 44 | icon: <BookOpen className="text-foreground-lighter" size={13} />, |
| 45 | label: 'Integrations docs', |
| 46 | href: `${DOCS_URL}/guides/integrations`, |
| 47 | }, |
| 48 | { |
| 49 | icon: <PlusSquare className="text-foreground-lighter" size={13} />, |
| 50 | label: 'Build an integration', |
| 51 | href: `${DOCS_URL}/guides/integrations/build-a-briven-oauth-integration`, |
| 52 | }, |
| 53 | ] |
| 54 | |
| 55 | export const MarketplaceSidebar = () => { |
| 56 | const router = useRouter() |
| 57 | const { ref } = useParams() |
| 58 | |
| 59 | const { installedIntegrations } = useInstalledIntegrations() |
| 60 | |
| 61 | const baseHref = `/project/${ref}/integrations` |
| 62 | const isDiscoverActive = |
| 63 | router.pathname === '/project/[ref]/integrations' && !router.query.type && !router.query.source |
| 64 | const activeIntegrationId = typeof router.query.id === 'string' ? router.query.id : undefined |
| 65 | |
| 66 | return ( |
| 67 | <aside className="grow flex h-full w-full flex-col gap-y-0.5 overflow-y-auto p-4 text-sm"> |
| 68 | <SidebarLink |
| 69 | href={baseHref} |
| 70 | active={isDiscoverActive} |
| 71 | icon={<LayoutGrid size={13} />} |
| 72 | label="Marketplace" |
| 73 | /> |
| 74 | |
| 75 | {installedIntegrations.length > 0 && ( |
| 76 | <> |
| 77 | <div className={sectionLabelCls}>Installed · {installedIntegrations.length}</div> |
| 78 | {installedIntegrations.map((integration) => { |
| 79 | const isActive = activeIntegrationId === integration.id |
| 80 | return ( |
| 81 | <Link |
| 82 | key={integration.id} |
| 83 | href={`${baseHref}/${integration.id}/overview`} |
| 84 | aria-current={isActive ? 'page' : undefined} |
| 85 | className={cn( |
| 86 | 'flex items-center gap-2 rounded px-2 py-1 text-sm', |
| 87 | 'text-foreground-light hover:bg-surface-200 hover:text-foreground', |
| 88 | isActive && 'bg-surface-200 text-foreground' |
| 89 | )} |
| 90 | > |
| 91 | <span className="relative flex h-[18px] w-[18px] shrink-0 items-center justify-center overflow-hidden rounded-[4px] border bg-white"> |
| 92 | {integration.icon({ className: 'p-0.5' })} |
| 93 | </span> |
| 94 | <span className="truncate">{integration.name}</span> |
| 95 | </Link> |
| 96 | ) |
| 97 | })} |
| 98 | </> |
| 99 | )} |
| 100 | |
| 101 | <div className={sectionLabelCls}>Resources</div> |
| 102 | {HELP_LINKS.map(({ icon, label, href }) => ( |
| 103 | <a |
| 104 | key={label} |
| 105 | href={href} |
| 106 | target="_blank" |
| 107 | rel="noreferrer" |
| 108 | className="flex items-center justify-between rounded px-2 py-1 text-sm text-foreground-light hover:bg-surface-200 hover:text-foreground" |
| 109 | > |
| 110 | <span className="flex items-center gap-2"> |
| 111 | {icon} |
| 112 | {label} |
| 113 | </span> |
| 114 | <ArrowUpRight size={11} className="opacity-50" /> |
| 115 | </a> |
| 116 | ))} |
| 117 | </aside> |
| 118 | ) |
| 119 | } |