Home.tsx152 lines · main
1import { DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
2import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
3import { IS_PLATFORM, useFlag, useParams } from 'common'
4import dayjs from 'dayjs'
5import { useEffect, useRef } from 'react'
6import { cn } from 'ui'
7
8import { AdvisorSection } from './AdvisorSection'
9import { ConnectSection } from './ConnectSection'
10import { CustomReportSection } from './CustomReportSection'
11import { DEFAULT_SECTION_ORDER, mergeSectionOrder } from './Home.utils'
12import { ProjectUsageSection as ProjectUsageSectionV2 } from './ProjectUsageSection'
13import { ProjectUsageSection as ProjectUsageSectionV1 } from '@/components/interfaces/Home/ProjectUsageSection'
14import { SortableSection } from '@/components/interfaces/ProjectHome/SortableSection'
15import { TopSection } from '@/components/interfaces/ProjectHome/TopSection'
16import { ProjectNeedsSecuring } from '@/components/layouts/ProjectNeedsSecuring/ProjectNeedsSecuring'
17import { ScaffoldContainer, ScaffoldSection } from '@/components/layouts/Scaffold'
18import { useLocalStorage } from '@/hooks/misc/useLocalStorage'
19import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
20import { PROJECT_STATUS } from '@/lib/constants'
21import { useTrack } from '@/lib/telemetry/track'
22import { useAppStateSnapshot } from '@/state/app-state'
23
24export const ProjectHome = () => {
25 const { enableBranching } = useParams()
26 const snap = useAppStateSnapshot()
27 const { data: project } = useSelectedProjectQuery()
28 const track = useTrack()
29
30 const showHomepageUsageV2 = useFlag('newHomepageUsageV2')
31
32 const isMatureProject = dayjs(project?.inserted_at).isBefore(dayjs().subtract(10, 'day'))
33
34 const hasShownEnableBranchingModalRef = useRef(false)
35 const isPaused = project?.status === PROJECT_STATUS.INACTIVE
36 const isComingUp = project?.status === PROJECT_STATUS.COMING_UP
37
38 const [sectionOrder, setSectionOrder] = useLocalStorage<string[]>(
39 `home-section-order-${project?.ref || 'default'}`,
40 DEFAULT_SECTION_ORDER
41 )
42
43 const UsageSection = showHomepageUsageV2 ? ProjectUsageSectionV2 : ProjectUsageSectionV1
44
45 const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } }))
46
47 const handleDragEnd = (event: DragEndEvent) => {
48 const { active, over } = event
49 if (!over || active.id === over.id) return
50 setSectionOrder((items) => {
51 const oldIndex = items.indexOf(String(active.id))
52 const newIndex = items.indexOf(String(over.id))
53 if (oldIndex === -1 || newIndex === -1) return items
54
55 track('home_section_rows_moved', {
56 section_moved: String(active.id),
57 old_position: oldIndex,
58 new_position: newIndex,
59 })
60
61 return arrayMove(items, oldIndex, newIndex)
62 })
63 }
64
65 useEffect(() => {
66 if (enableBranching && !hasShownEnableBranchingModalRef.current) {
67 hasShownEnableBranchingModalRef.current = true
68 snap.setShowCreateBranchModal(true)
69 }
70 }, [enableBranching, snap])
71
72 useEffect(() => {
73 setSectionOrder(mergeSectionOrder)
74 }, [setSectionOrder])
75
76 const showConnectSection = !isMatureProject && !!project
77
78 const renderOrder = mergeSectionOrder(sectionOrder).filter((id) => {
79 if (id === 'connect') return showConnectSection
80 return true
81 })
82
83 return (
84 <ProjectNeedsSecuring>
85 <div className="w-full h-full">
86 <ScaffoldContainer size="large" className={cn(isPaused && 'h-full')}>
87 <ScaffoldSection
88 isFullWidth
89 className={cn(isPaused ? 'h-full flex justify-center p-0!' : 'pb-0')}
90 >
91 <TopSection />
92 </ScaffoldSection>
93 </ScaffoldContainer>
94 {!isPaused && (
95 <ScaffoldContainer size="large">
96 <ScaffoldSection isFullWidth className="gap-12 pb-32">
97 <DndContext sensors={sensors} onDragEnd={handleDragEnd}>
98 <SortableContext items={renderOrder} strategy={verticalListSortingStrategy}>
99 {renderOrder.map((id) => {
100 if (IS_PLATFORM && id === 'usage') {
101 return (
102 <div
103 key={id}
104 className={cn(isComingUp && 'opacity-60 pointer-events-none')}
105 >
106 <SortableSection id={id}>
107 <UsageSection />
108 </SortableSection>
109 </div>
110 )
111 }
112 if (id === 'connect' && showConnectSection) {
113 return (
114 <SortableSection key={id} id={id}>
115 <ConnectSection />
116 </SortableSection>
117 )
118 }
119 if (id === 'advisor') {
120 return (
121 <div
122 key={id}
123 className={cn(isComingUp && 'opacity-60 pointer-events-none')}
124 >
125 <SortableSection id={id}>
126 <AdvisorSection showEmptyState={isComingUp} />
127 </SortableSection>
128 </div>
129 )
130 }
131 if (id === 'custom-report') {
132 return (
133 <div
134 key={id}
135 className={cn(isComingUp && 'opacity-60 pointer-events-none')}
136 >
137 <SortableSection id={id}>
138 <CustomReportSection />
139 </SortableSection>
140 </div>
141 )
142 }
143 })}
144 </SortableContext>
145 </DndContext>
146 </ScaffoldSection>
147 </ScaffoldContainer>
148 )}
149 </div>
150 </ProjectNeedsSecuring>
151 )
152}