import { useParams } from 'common'
import { Check, ExternalLink, FlaskConical, Square } from 'lucide-react'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import { useLatest } from 'react-use'
import { toast } from 'sonner'
import { Badge } from 'ui'
import {
PageType,
useRegisterCommands,
useRegisterPage,
useSetCommandMenuOpen,
useSetPage,
} from 'ui-patterns/CommandMenu'
import { COMMAND_MENU_SECTIONS } from './CommandMenu.utils'
import {
useFeaturePreviewContext,
useFeaturePreviewModal,
} from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import {
FeaturePreview,
useVisibleFeaturePreviewsByCategory,
} from '@/components/interfaces/App/FeaturePreview/useFeaturePreviews'
import { useBannerStack } from '@/components/ui/BannerStack/BannerStackProvider'
const FEATURE_PREVIEWS_PAGE_NAME = 'Feature previews'
export function useFeaturePreviewCommands() {
const router = useRouter()
const { ref } = useParams()
const { dismissBanner } = useBannerStack()
const setPage = useSetPage()
const setIsOpen = useSetCommandMenuOpen()
const { flags, onUpdateFlag } = useFeaturePreviewContext()
const { selectFeaturePreview, toggleFeaturePreviewModal } = useFeaturePreviewModal()
const openFeaturePreviewDetails = (key: string) => {
selectFeaturePreview(key)
toggleFeaturePreviewModal(true)
setIsOpen(false)
}
// Registering this page tears it down and re-adds it (popping it off the
// page stack if it's currently open) whenever `deps` changes identity, so
// `flags` is read through a ref instead of being a dep — toggling a preview
// must not kick the user back to the root menu. Same reasoning applies to
// `ref`/`pathname`: the command's `action` closure is only recreated when
// `previewsByCategory` changes identity, so without reading these through a
// ref too, navigating between projects (or to/from the org view) after that
// page was first registered would keep routing to whatever project was
// current back then.
const flagsRef = useLatest(flags)
const routeContextRef = useLatest({ ref, pathname: router.pathname })
// Mirrors FeaturePreviewModal's toggleFeature: if the preview has a route to
// try it out in, enabling it takes the user there instead of just flipping
// the flag silently.
const toggleFeaturePreview = (preview: FeaturePreview) => {
if (preview.isForced) return
const isEnabling = !flagsRef.current[preview.key]
onUpdateFlag(preview.key, isEnabling)
if (!isEnabling) {
toast(`${preview.name} disabled`)
return
}
// Only route into a project when actually on a project-scoped page —
// `ref` alone isn't enough, since it can retain a stale value after
// client-side navigating to a route without a `ref` segment (e.g. the org
// view).
const { ref: currentRef, pathname } = routeContextRef.current
const isProjectScopedRoute = pathname.startsWith('/project/')
const route = isProjectScopedRoute ? preview.getRoute?.(currentRef) : undefined
if (route !== undefined && currentRef !== undefined) {
setIsOpen(false)
router.push(route)
toast.success(`${preview.name} enabled`, {
description: "We've taken you to where you can try it out.",
})
if (preview.bannerId) dismissBanner(preview.bannerId)
} else {
toast.success(`${preview.name} enabled`, {
description: "It's now active across the dashboard.",
})
}
}
// Grouped identically to the feature preview modal's category accordion —
// shared hook, so the two can never list a different set of previews.
const previewsByCategory = useVisibleFeaturePreviewsByCategory()
const visiblePreviews = useMemo(
() => previewsByCategory.flatMap(({ previews }) => previews),
[previewsByCategory]
)
useRegisterPage(
FEATURE_PREVIEWS_PAGE_NAME,
{
type: PageType.Commands,
sections: previewsByCategory
.filter(({ previews }) => previews.length > 0)
.map(({ category, previews }) => ({
id: `feature-previews-${category ?? 'others'}`,
name: category ?? 'Others',
commands: previews.flatMap((preview) => [
{
id: `feature-preview-${preview.key}`,
name: preview.name,
value: preview.isForced
? `${preview.name}, Feature preview, now the default, can't be turned off`
: `${preview.name}, Feature preview, Toggle ${preview.name}`,
action: () => toggleFeaturePreview(preview),
icon: () => (flagsRef.current[preview.key] ? : ),
badge: preview.isForced
? () => (
Default
)
: preview.isNew
? () => New
: undefined,
},
{
id: `feature-preview-${preview.key}-details`,
name: `${preview.name}: View details`,
value: `${preview.name}, About, Learn more, Description, Feedback, Discussion`,
action: () => openFeaturePreviewDetails(preview.key),
icon: () => ,
defaultHidden: true,
},
]),
})),
},
{ deps: [previewsByCategory], enabled: visiblePreviews.length > 0 }
)
useRegisterCommands(
COMMAND_MENU_SECTIONS.ACTIONS,
[
{
id: 'feature-previews',
name: 'Feature previews...',
value: 'Feature previews, Try new features, Early access, Opt in, Opt out, Beta features',
action: () => setPage(FEATURE_PREVIEWS_PAGE_NAME),
icon: () => ,
},
],
{ enabled: visiblePreviews.length > 0 }
)
}