import { DndContext, DragEndEvent, KeyboardSensor, PointerSensor, useSensor, useSensors, } from '@dnd-kit/core' import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, } from '@dnd-kit/sortable' import { useFlag, useParams } from 'common' import dayjs from 'dayjs' import { useEffect, useRef } from 'react' import { cn } from 'ui' import { AdvisorSection } from './AdvisorSection' import { ConnectSection } from './ConnectSection' import { CustomReportSection } from './CustomReportSection' import { DEFAULT_SECTION_ORDER, mergeSectionOrder } from './Home.utils' import { NotebooksSection } from './NotebooksSection' import { ProjectUsageSection } from './ProjectUsageSection' import { ProjectUsageSectionDeltas } from './ProjectUsageSectionDeltas' import { useIsExplorerEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext' import { TopSection } from '@/components/interfaces/ProjectHome/TopSection' import { ProjectNeedsSecuring } from '@/components/layouts/ProjectNeedsSecuring/ProjectNeedsSecuring' import { ScaffoldContainer, ScaffoldSection } from '@/components/layouts/Scaffold' import { SortableSection } from '@/components/ui/SortableSection' import { useLocalStorage } from '@/hooks/misc/useLocalStorage' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { IS_PLATFORM, PROJECT_STATUS } from '@/lib/constants' import { useTrack } from '@/lib/telemetry/track' import { useAppStateSnapshot } from '@/state/app-state' const SORT_GRIP_CLASS = 'absolute -left-10' export const ProjectHome = () => { const { enableBranching } = useParams() const snap = useAppStateSnapshot() const { data: project } = useSelectedProjectQuery() const track = useTrack() const showHomepageUsageDeltas = useFlag('newHomepageUsageDeltas') const isExplorerEnabled = useIsExplorerEnabled() const isMatureProject = dayjs(project?.inserted_at).isBefore(dayjs().subtract(10, 'day')) const hasShownEnableBranchingModalRef = useRef(false) const isPaused = project?.status === PROJECT_STATUS.INACTIVE const isComingUp = project?.status === PROJECT_STATUS.COMING_UP const [sectionOrder, setSectionOrder] = useLocalStorage( `home-section-order-${project?.ref || 'default'}`, DEFAULT_SECTION_ORDER ) const UsageSection = showHomepageUsageDeltas ? ProjectUsageSectionDeltas : ProjectUsageSection const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) ) const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event if (!over || active.id === over.id) return setSectionOrder((items) => { const oldIndex = items.indexOf(String(active.id)) const newIndex = items.indexOf(String(over.id)) if (oldIndex === -1 || newIndex === -1) return items track('home_section_rows_moved', { section_moved: String(active.id), old_position: oldIndex, new_position: newIndex, }) return arrayMove(items, oldIndex, newIndex) }) } useEffect(() => { if (enableBranching && !hasShownEnableBranchingModalRef.current) { hasShownEnableBranchingModalRef.current = true snap.setShowCreateBranchModal(true) } }, [enableBranching, snap]) useEffect(() => { setSectionOrder(mergeSectionOrder) }, [setSectionOrder]) // On self-hosted the project's `inserted_at` is hard-coded to a past date // (so it always looks "mature"), and the home page is otherwise sparse — // always surface the Get Connected pane there. Platform keeps the // maturity gate so long-running projects don't see it forever. const showConnectSection = !!project && (!IS_PLATFORM || !isMatureProject) const renderOrder = mergeSectionOrder(sectionOrder).filter((id) => { if (id === 'connect') return showConnectSection if (id === 'usage' || id === 'custom-report') return IS_PLATFORM return true }) return (
{!isPaused && ( {renderOrder.map((id) => { if (IS_PLATFORM && id === 'usage') { return (
) } if (id === 'connect' && showConnectSection) { return ( ) } if (id === 'advisor') { return (
) } if (IS_PLATFORM && id === 'custom-report') { // Notebooks take over the reports slot (and its saved position) under the Explorer preview return (
{isExplorerEnabled ? : }
) } })}
)}
) }