mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
<img width="1758" height="1252" alt="image" src="https://github.com/user-attachments/assets/6f5ff072-9b47-4265-924d-0ca22368f6cb" /> ## What With the Explorer feature preview on, the homepage **Reports** row becomes a **Notebooks** row showing the four newest notebooks. - Advisor and notebook cards share a new `HomeCard` layout: same height and header, content anchored to the bottom, 3-line description clamp. - Notebook cards show the cell count, the creation date, and an **Analyze** button that starts the same chat as the Explorer notebook tab (`useAnalyzeNotebook`, now shared by both). - Advisor lint cards replace the assistant dropdown with two icon buttons: **Ask Assistant** and **Copy prompt**. Copy prompt shows a check after copying. - The empty state matches the Reports one. ## How to test 1. Turn on the Explorer feature preview and open a project's Home page. 2. The Reports row should be replaced by Notebooks. With no notebooks, you should see the empty state; **Create your first notebook** opens a new notebook in Explorer. 3. Click a notebook card to open it. Click **Analyze** to start an assistant chat. It should be disabled on a notebook with no cells. 4. On an advisor lint card, check **Ask Assistant** opens the assistant with the lint prompt, and **Copy prompt** copies it and briefly shows a check. 5. Turn the preview off: the Reports row comes back unchanged. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Project home now displays up to four notebooks, with options to create a notebook, open it, and see its cell count. * Start an AI analysis of a notebook directly from its card or from the notebook explorer. Analysis is unavailable for notebooks without cells. * Advisor cards now offer a one-click AI chat and a copyable prompt. * Project home shows notebooks when the Explorer preview is enabled; otherwise, it continues to show custom reports. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
179 lines
6.9 KiB
TypeScript
179 lines
6.9 KiB
TypeScript
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<string[]>(
|
|
`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 (
|
|
<ProjectNeedsSecuring>
|
|
<div className="w-full h-full">
|
|
<ScaffoldContainer size="large" className={cn(isPaused && 'h-full')}>
|
|
<ScaffoldSection
|
|
isFullWidth
|
|
className={cn(isPaused ? 'h-full flex justify-center p-0!' : 'pb-0')}
|
|
>
|
|
<TopSection />
|
|
</ScaffoldSection>
|
|
</ScaffoldContainer>
|
|
{!isPaused && (
|
|
<ScaffoldContainer size="large">
|
|
<ScaffoldSection isFullWidth className="gap-12 pb-32">
|
|
<DndContext sensors={sensors} onDragEnd={handleDragEnd}>
|
|
<SortableContext items={renderOrder} strategy={verticalListSortingStrategy}>
|
|
{renderOrder.map((id) => {
|
|
if (IS_PLATFORM && id === 'usage') {
|
|
return (
|
|
<div
|
|
key={id}
|
|
className={cn(isComingUp && 'opacity-60 pointer-events-none')}
|
|
>
|
|
<SortableSection gripClassName={SORT_GRIP_CLASS} id={id}>
|
|
<UsageSection />
|
|
</SortableSection>
|
|
</div>
|
|
)
|
|
}
|
|
if (id === 'connect' && showConnectSection) {
|
|
return (
|
|
<SortableSection gripClassName={SORT_GRIP_CLASS} key={id} id={id}>
|
|
<ConnectSection />
|
|
</SortableSection>
|
|
)
|
|
}
|
|
if (id === 'advisor') {
|
|
return (
|
|
<div
|
|
key={id}
|
|
className={cn(isComingUp && 'opacity-60 pointer-events-none')}
|
|
>
|
|
<SortableSection gripClassName={SORT_GRIP_CLASS} id={id}>
|
|
<AdvisorSection showEmptyState={isComingUp} />
|
|
</SortableSection>
|
|
</div>
|
|
)
|
|
}
|
|
if (IS_PLATFORM && id === 'custom-report') {
|
|
// Notebooks take over the reports slot (and its saved position) under the Explorer preview
|
|
return (
|
|
<div
|
|
key={id}
|
|
className={cn(isComingUp && 'opacity-60 pointer-events-none')}
|
|
>
|
|
<SortableSection gripClassName={SORT_GRIP_CLASS} id={id}>
|
|
{isExplorerEnabled ? <NotebooksSection /> : <CustomReportSection />}
|
|
</SortableSection>
|
|
</div>
|
|
)
|
|
}
|
|
})}
|
|
</SortableContext>
|
|
</DndContext>
|
|
</ScaffoldSection>
|
|
</ScaffoldContainer>
|
|
)}
|
|
</div>
|
|
</ProjectNeedsSecuring>
|
|
)
|
|
}
|