ConnectSection.tsx112 lines · main
1import { IS_PLATFORM } from 'common'
2import { ChevronRight } from 'lucide-react'
3import { useRouter } from 'next/router'
4import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs'
5import { useEffect, useRef } from 'react'
6import { Card, CardContent, cn } from 'ui'
7
8import { useAvailableConnectModes } from '../ConnectSheet/useAvailableConnectModes'
9import { CONNECT_ACTIONS } from './ConnectSection.config'
10import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
11import { BASE_PATH, PROJECT_STATUS } from '@/lib/constants'
12import { useTrack } from '@/lib/telemetry/track'
13import { useAppStateSnapshot } from '@/state/app-state'
14
15export const ConnectSection = () => {
16 const router = useRouter()
17 const { data: selectedProject } = useSelectedProjectQuery()
18 const { setConnectSheetSource } = useAppStateSnapshot()
19 const track = useTrack()
20 const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false))
21 const [, setConnectTab] = useQueryState('connectTab', parseAsString)
22
23 const isActiveHealthy = selectedProject?.status === PROJECT_STATUS.ACTIVE_HEALTHY
24
25 const availableModeIds = useAvailableConnectModes()
26 const availableActions = CONNECT_ACTIONS.filter((action) =>
27 action.mode ? availableModeIds.includes(action.mode) : true
28 )
29
30 const hasTrackedExposure = useRef(false)
31
32 useEffect(() => {
33 if (!IS_PLATFORM) return
34 if (hasTrackedExposure.current) return
35 hasTrackedExposure.current = true
36 track('home_connect_section_exposed')
37 }, [track])
38
39 const handleActionClick = (action: (typeof CONNECT_ACTIONS)[number]) => {
40 track('home_connect_action_clicked', { mode: action.id })
41
42 if (action.mode) {
43 setConnectTab(action.mode)
44 setConnectSheetSource('connect_section')
45 setShowConnect(true)
46 return
47 }
48
49 if (action.href && selectedProject?.ref) {
50 router.push(action.href.replace('[ref]', selectedProject.ref))
51 }
52 }
53
54 return (
55 <section className="w-full">
56 <div className="mb-6">
57 <h3 className="heading-section">Get connected</h3>
58 </div>
59
60 <Card className="bg-background/25 border-dashed relative overflow-hidden">
61 <div className="absolute -inset-16 z-0 opacity-50">
62 <img
63 src={`${BASE_PATH}/img/reports/bg-grafana-dark.svg`}
64 alt=""
65 className="w-full h-full object-cover object-right hidden dark:block"
66 />
67 <img
68 src={`${BASE_PATH}/img/reports/bg-grafana-light.svg`}
69 alt=""
70 className="w-full h-full object-cover object-right dark:hidden"
71 />
72 <div className="absolute inset-0 bg-linear-to-r from-background-alternative to-transparent" />
73 </div>
74
75 <CardContent className="relative z-10 p-0">
76 <div className="grid grid-cols-1 xl:grid-cols-5 divide-y xl:divide-y-0 xl:divide-x border-muted">
77 {availableActions.map((action) => (
78 <button
79 key={action.id}
80 type="button"
81 disabled={
82 (action.requiresActiveProject ?? true) ? !isActiveHealthy : !selectedProject?.ref
83 }
84 onClick={() => handleActionClick(action)}
85 className={cn(
86 'group flex items-center gap-3 p-4 text-left transition-colors min-h-[72px] w-full',
87 'hover:bg-surface-100 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-brand',
88 'xl:min-h-32 xl:flex-col xl:justify-center xl:p-6 xl:text-center',
89 ((action.requiresActiveProject ?? true)
90 ? !isActiveHealthy
91 : !selectedProject?.ref) && 'cursor-not-allowed opacity-50'
92 )}
93 >
94 <span className="shrink-0 text-foreground-light group-hover:text-foreground">
95 {action.icon}
96 </span>
97 <div className="flex min-w-0 flex-1 flex-col gap-1 xl:flex-initial">
98 <p className="text-sm">{action.heading}</p>
99 <p className="text-sm text-foreground-lighter">{action.subheading}</p>
100 </div>
101 <ChevronRight
102 size={16}
103 className="shrink-0 text-foreground-lighter group-hover:text-foreground xl:hidden"
104 />
105 </button>
106 ))}
107 </div>
108 </CardContent>
109 </Card>
110 </section>
111 )
112}