mirror of
https://github.com/supabase/supabase.git
synced 2026-06-12 17:27:58 +08:00
## Problem The `_Shadcn_` suffix isn't needed anymore on `Command` components ## Solution - Remove the `_Shadcn_` suffix - Simplify UI package exports - Apply prettier <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Simplified command component imports and exports across the UI library by removing internal naming aliases and adopting direct component references. Updated the public UI package barrel export to use wildcard re-exports for cleaner API surface. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46153?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
247 lines
9.7 KiB
TypeScript
247 lines
9.7 KiB
TypeScript
import { useParams } from 'common'
|
|
import { noop } from 'lodash'
|
|
import { Check, ChevronDown, Loader2, Plus } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { useRouter } from 'next/router'
|
|
import { parseAsBoolean, useQueryState } from 'nuqs'
|
|
import { useEffect, useState } from 'react'
|
|
import {
|
|
Button,
|
|
ButtonProps,
|
|
cn,
|
|
Command,
|
|
CommandGroup,
|
|
CommandItem,
|
|
CommandList,
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
ScrollArea,
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from 'ui'
|
|
|
|
import { Markdown } from '@/components/interfaces/Markdown'
|
|
import { REPLICA_STATUS } from '@/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants'
|
|
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
|
|
import { formatDatabaseID, formatDatabaseRegion } from '@/data/read-replicas/replicas.utils'
|
|
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
|
import { IS_PLATFORM } from '@/lib/constants'
|
|
import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
|
|
|
|
interface DatabaseSelectorProps {
|
|
selectedDatabaseId?: string // To override initial state
|
|
variant?: 'regular' | 'connected-on-right' | 'connected-on-left' | 'connected-on-both'
|
|
additionalOptions?: { id: string; name: string }[]
|
|
buttonProps?: ButtonProps
|
|
onSelectId?: (id: string) => void // Optional callback
|
|
className?: string
|
|
align?: 'start' | 'end'
|
|
isForm?: boolean
|
|
}
|
|
|
|
export const DatabaseSelector = ({
|
|
selectedDatabaseId: _selectedDatabaseId,
|
|
variant = 'regular',
|
|
additionalOptions = [],
|
|
onSelectId = noop,
|
|
buttonProps,
|
|
align = 'end',
|
|
className,
|
|
isForm = false,
|
|
}: DatabaseSelectorProps) => {
|
|
const router = useRouter()
|
|
const { ref: projectRef } = useParams()
|
|
const [open, setOpen] = useState(false)
|
|
const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false))
|
|
|
|
const { infrastructureReadReplicas } = useIsFeatureEnabled(['infrastructure:read_replicas'])
|
|
|
|
const state = useDatabaseSelectorStateSnapshot()
|
|
const selectedDatabaseId = _selectedDatabaseId ?? state.selectedDatabaseId
|
|
|
|
const { data, isPending: isLoading, isSuccess } = useReadReplicasQuery({ projectRef })
|
|
const databases = data ?? []
|
|
const sortedDatabases = databases
|
|
.sort((a, b) => (a.inserted_at > b.inserted_at ? 1 : 0))
|
|
.sort((database) => (database.identifier === projectRef ? -1 : 0))
|
|
|
|
const selectedDatabase = databases.find((db) => db.identifier === selectedDatabaseId)
|
|
const selectedDatabaseRegion = formatDatabaseRegion(selectedDatabase?.region ?? '')
|
|
const formattedDatabaseId = formatDatabaseID(selectedDatabaseId ?? '')
|
|
|
|
const selectedAdditionalOption = additionalOptions.find((x) => x.id === selectedDatabaseId)
|
|
|
|
const newReplicaURL = `/project/${projectRef}/database/replication?type=Read+Replica`
|
|
|
|
useEffect(() => {
|
|
if (_selectedDatabaseId && !isForm) state.setSelectedDatabaseId(_selectedDatabaseId)
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [_selectedDatabaseId])
|
|
|
|
return (
|
|
<Popover open={open} onOpenChange={setOpen} modal={false}>
|
|
<PopoverTrigger asChild>
|
|
<div className={cn('flex cursor-pointer', className)}>
|
|
{!isForm && (
|
|
<span className="flex items-center text-foreground-lighter px-3 rounded-lg rounded-r-none text-xs border border-button border-r-0">
|
|
Source
|
|
</span>
|
|
)}
|
|
<Button
|
|
type="default"
|
|
icon={isLoading && <Loader2 className="animate-spin" />}
|
|
iconRight={<ChevronDown strokeWidth={1.5} size={12} />}
|
|
{...buttonProps}
|
|
className={cn(
|
|
'justify-start',
|
|
!isForm && 'rounded-l-none',
|
|
variant === 'connected-on-right' && 'rounded-r-none',
|
|
variant === 'connected-on-left' && 'rounded-l-none border-l-0',
|
|
variant === 'connected-on-both' && 'rounded-none border-x-0',
|
|
buttonProps?.className
|
|
)}
|
|
>
|
|
{selectedAdditionalOption ? (
|
|
<span>{selectedAdditionalOption.name}</span>
|
|
) : (
|
|
<>
|
|
<span className="capitalize">
|
|
{isLoading || selectedDatabase?.identifier === projectRef
|
|
? 'Primary database'
|
|
: 'Read replica'}
|
|
</span>{' '}
|
|
{isSuccess && selectedDatabase?.identifier !== projectRef && (
|
|
<span>
|
|
({selectedDatabaseRegion} - {formattedDatabaseId})
|
|
</span>
|
|
)}
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="p-0 w-64" side="bottom" align={align}>
|
|
<Command>
|
|
<CommandList>
|
|
{additionalOptions.length > 0 && (
|
|
<CommandGroup className="border-b">
|
|
{additionalOptions.map((option) => (
|
|
<CommandItem
|
|
key={option.id}
|
|
value={option.id}
|
|
className="cursor-pointer w-full"
|
|
onSelect={() => {
|
|
if (!isForm) state.setSelectedDatabaseId(option.id)
|
|
setOpen(false)
|
|
onSelectId(option.id)
|
|
}}
|
|
onClick={() => {
|
|
if (!isForm) state.setSelectedDatabaseId(option.id)
|
|
setOpen(false)
|
|
onSelectId(option.id)
|
|
}}
|
|
>
|
|
<div className="w-full flex items-center justify-between">
|
|
<p>{option.name}</p>
|
|
{option.id === selectedDatabaseId && <Check size={14} />}
|
|
</div>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
)}
|
|
<CommandGroup>
|
|
<ScrollArea className={(databases || []).length > 7 ? 'h-[210px]' : ''}>
|
|
{sortedDatabases?.map((database) => {
|
|
const region = formatDatabaseRegion(database.region)
|
|
const id = formatDatabaseID(database.identifier)
|
|
|
|
if (database.status !== 'ACTIVE_HEALTHY') {
|
|
const status = [
|
|
REPLICA_STATUS.INIT_READ_REPLICA,
|
|
REPLICA_STATUS.COMING_UP,
|
|
].includes(database.status)
|
|
? 'coming up'
|
|
: 'not healthy'
|
|
|
|
return (
|
|
<Tooltip key={database.identifier}>
|
|
<TooltipTrigger asChild>
|
|
<div className="px-2 py-1.5 w-full flex items-center justify-between">
|
|
<p className="text-xs text-foreground-lighter">
|
|
Read replica ({region} - {id})
|
|
</p>
|
|
</div>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="right" className="w-80">
|
|
<Markdown
|
|
className="text-xs text-foreground"
|
|
content={`Replica unable to accept requests as its ${status}. [View infrastructure settings](/project/${projectRef}/settings/infrastructure) for more information.`}
|
|
/>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<CommandItem
|
|
key={database.identifier}
|
|
value={database.identifier}
|
|
className="cursor-pointer w-full"
|
|
onSelect={() => {
|
|
if (!isForm) state.setSelectedDatabaseId(database.identifier)
|
|
setOpen(false)
|
|
onSelectId(database.identifier)
|
|
}}
|
|
onClick={() => {
|
|
if (!isForm) state.setSelectedDatabaseId(database.identifier)
|
|
setOpen(false)
|
|
onSelectId(database.identifier)
|
|
}}
|
|
>
|
|
<div className="w-full flex items-center justify-between">
|
|
<p>
|
|
{database.identifier === projectRef
|
|
? 'Primary database'
|
|
: `Read replica (${region} - ${id})`}
|
|
</p>
|
|
{database.identifier === selectedDatabaseId && <Check size={16} />}
|
|
</div>
|
|
</CommandItem>
|
|
)
|
|
})}
|
|
</ScrollArea>
|
|
</CommandGroup>
|
|
{IS_PLATFORM && infrastructureReadReplicas && (
|
|
<CommandGroup className="border-t">
|
|
<CommandItem
|
|
className="cursor-pointer w-full"
|
|
onSelect={() => {
|
|
setOpen(false)
|
|
router.push(newReplicaURL)
|
|
}}
|
|
onClick={() => setOpen(false)}
|
|
>
|
|
<Link
|
|
href={newReplicaURL}
|
|
onClick={async () => {
|
|
setOpen(false)
|
|
// [Joshen] This is used in the Connect UI which is available across all pages
|
|
setShowConnect(false)
|
|
}}
|
|
className="w-full flex items-center gap-2"
|
|
>
|
|
<Plus size={14} strokeWidth={1.5} />
|
|
<p>Create a new read replica</p>
|
|
</Link>
|
|
</CommandItem>
|
|
</CommandGroup>
|
|
)}
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
)
|
|
}
|