MarketplaceSidebar.tsx119 lines · main
1import { useParams } from 'common'
2import { ArrowUpRight, BookOpen, LayoutGrid, PlusSquare } from 'lucide-react'
3import Link from 'next/link'
4import { useRouter } from 'next/router'
5import { cn } from 'ui'
6
7import { useInstalledIntegrations } from '@/components/interfaces/Integrations/Landing/useInstalledIntegrations'
8import { DOCS_URL } from '@/lib/constants'
9
10const sectionLabelCls =
11 'px-2 pt-4 pb-1.5 font-mono text-xs uppercase tracking-wider text-foreground-lighter'
12
13interface SidebarLinkProps {
14 href: string
15 active?: boolean
16 icon?: React.ReactNode
17 label: React.ReactNode
18 count?: number | string
19 className?: string
20}
21
22const 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
42const 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
55export 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}