chore: only load data in modals when they're visible (#34617)

* chore: only load data in modals when they're visible

* SidePanelGitHubRepoLinker

* scrolling is not my strong suit

* simple loading state for add new secret

* fix button width

* update to use shadcn

* fix ts
This commit is contained in:
Alaister Young authored and GitHub committed 2025-04-02 16:41:47 +08:00
1 parent 5fb29be685
commit e3dae9ec36
16 files changed
+195 -137

No files matched your search

@@ -37,10 +37,13 @@ const EnableExtensionModal = ({ visible, extension, onCancel }: EnableExtensionM
const [defaultSchema, setDefaultSchema] = useState()
const [fetchingSchemaInfo, setFetchingSchemaInfo] = useState(false)
const { data: schemas, isLoading: isSchemasLoading } = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const { data: schemas, isLoading: isSchemasLoading } = useSchemasQuery(
{
projectRef: project?.ref,
connectionString: project?.connectionString,
},
{ enabled: visible }
)
const { mutate: enableExtension, isLoading: isEnabling } = useDatabaseExtensionEnableMutation({
onSuccess: () => {
toast.success(`Extension "${extension.name}" is now enabled`)
@@ -1,12 +1,18 @@
import { noop } from 'lodash'
import { Plus } from 'lucide-react'
import { Input, Listbox, Modal } from 'ui'
import {
Input,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
SelectItem_Shadcn_,
SelectTrigger_Shadcn_,
Select_Shadcn_,
} from 'ui'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { usePgSodiumKeysQuery } from 'data/pg-sodium-keys/pg-sodium-keys-query'
interface EncryptionKeySelectorProps {
id?: string
nameId?: string
label?: string
labelOptional?: string
@@ -17,7 +23,6 @@ interface EncryptionKeySelectorProps {
}
const EncryptionKeySelector = ({
id = 'keyId',
nameId = 'keyName',
label = 'Encryption key',
labelOptional,
@@ -37,36 +42,46 @@ const EncryptionKeySelector = ({
return (
<>
<Listbox
id={id}
label={label}
size="small"
defaultValue={selectedKeyId}
value={selectedKeyId}
labelOptional={labelOptional}
onChange={onSelectKey}
>
<Listbox.Option
key="create-new"
id="create-new"
value="create-new"
label="Create a new Encryption Key"
addOnBefore={() => <Plus size={16} strokeWidth={1.5} />}
>
Create a new Encryption Key
</Listbox.Option>
{keys.length > 0 && <Modal.Separator />}
{keys.map((key) => (
<Listbox.Option key={key.id} label={key.name} value={key.id}>
<div className="space-y-1">
<p>{key.name || 'No name provided'}</p>
<p className="text-xs">
ID: <span className="font-mono">{key.id}</span>
</p>
</div>
</Listbox.Option>
))}
</Listbox>
<div className="space-y-2">
{label && (
<div className="flex flex-row gap-x-2 justify-between">
<label className="block text-foreground-light text-sm break-all">{label}</label>
{labelOptional && (
<span className="text-foreground-lighter text-sm">{labelOptional}</span>
)}
</div>
)}
<Select_Shadcn_ value={selectedKeyId} onValueChange={onSelectKey}>
<SelectTrigger_Shadcn_ className="w-full">
{keys.find((key) => key.id === selectedKeyId)?.name || 'Select an encryption key'}
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectGroup_Shadcn_>
<SelectItem_Shadcn_ value="create-new">
<span className="flex items-center gap-2">
<Plus size={16} strokeWidth={1.5} />
Create a new Encryption Key
</span>
</SelectItem_Shadcn_>
{keys.length > 0 && (
<>
<div className="h-px bg-border my-2" />
{keys.map((key) => (
<SelectItem_Shadcn_ key={key.id} value={key.id}>
<div className="space-y-1">
<p>{key.name || 'No name provided'}</p>
<p className="text-xs">
ID: <span className="font-mono">{key.id}</span>
</p>
</div>
</SelectItem_Shadcn_>
))}
</>
)}
</SelectGroup_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
</div>
{selectedKeyId === 'create-new' && (
<Input
id={nameId}
@@ -3,12 +3,13 @@ import { toast } from 'sonner'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import InformationBox from 'components/ui/InformationBox'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { usePgSodiumKeyCreateMutation } from 'data/pg-sodium-keys/pg-sodium-key-create-mutation'
import { usePgSodiumKeysQuery } from 'data/pg-sodium-keys/pg-sodium-keys-query'
import { useVaultSecretCreateMutation } from 'data/vault/vault-secret-create-mutation'
import { Eye, EyeOff, HelpCircle } from 'lucide-react'
import { Button, Form, Input, Modal } from 'ui'
import EncryptionKeySelector from '../Keys/EncryptionKeySelector'
import { EyeOff, Eye, HelpCircle } from 'lucide-react'
interface AddNewSecretModalProps {
visible: boolean
@@ -23,10 +24,15 @@ const AddNewSecretModal = ({ visible, onClose }: AddNewSecretModalProps) => {
const { mutateAsync: addKeyMutation } = usePgSodiumKeyCreateMutation()
const { mutateAsync: addSecret } = useVaultSecretCreateMutation()
const { data: keys } = usePgSodiumKeysQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const { data: keys, isLoading } = usePgSodiumKeysQuery(
{
projectRef: project?.ref,
connectionString: project?.connectionString,
},
{
enabled: visible,
}
)
useEffect(() => {
if (visible && keys) {
@@ -87,76 +93,81 @@ const AddNewSecretModal = ({ visible, onClose }: AddNewSecretModalProps) => {
onCancel={onClose}
header="Add new secret"
>
<Form
id="add-new-secret-form"
initialValues={{ name: '', description: '', secret: '', keyId: '', keyName: '' }}
validate={validate}
validateOnBlur={false}
onSubmit={onAddNewSecret}
>
{({ isSubmitting }: any) => {
return (
<>
<Modal.Content className="space-y-4">
<Input id="name" label="Name" />
<Input id="description" label="Description" labelOptional="Optional" />
<Input
id="secret"
type={showSecretValue ? 'text' : 'password'}
label="Secret value"
actions={
<div className="mr-1">
<Button
type="default"
icon={showSecretValue ? <EyeOff /> : <Eye />}
onClick={() => setShowSecretValue(!showSecretValue)}
/>
</div>
}
/>
</Modal.Content>
<Modal.Separator />
<Modal.Content className="space-y-4">
<EncryptionKeySelector
id="keyId"
nameId="keyName"
label="Select a key to encrypt your secret with"
labelOptional="Optional"
selectedKeyId={selectedKeyId}
onSelectKey={setSelectedKeyId}
/>
<InformationBox
icon={<HelpCircle size={18} strokeWidth={2} />}
url="https://github.com/supabase/vault"
urlLabel="Vault documentation"
title="What is a key?"
description={
<div className="space-y-2">
<p>
Keys are used to encrypt data inside your database, and every secret in the
Vault is encrypted with a key.
</p>
<p>
You may create different keys for different purposes, such as one for
encrypting user data, and another for application data.
</p>
</div>
}
/>
</Modal.Content>
<Modal.Separator />
<Modal.Content className="flex items-center justify-end space-x-2">
<Button type="default" disabled={isSubmitting} onClick={onClose}>
Cancel
</Button>
<Button htmlType="submit" disabled={isSubmitting} loading={isSubmitting}>
Add secret
</Button>
</Modal.Content>
</>
)
}}
</Form>
{isLoading ? (
<Modal.Content>
<GenericSkeletonLoader />
</Modal.Content>
) : (
<Form
id="add-new-secret-form"
initialValues={{ name: '', description: '', secret: '', keyId: '', keyName: '' }}
validate={validate}
validateOnBlur={false}
onSubmit={onAddNewSecret}
>
{({ isSubmitting }: any) => {
return (
<>
<Modal.Content className="space-y-4">
<Input id="name" label="Name" />
<Input id="description" label="Description" labelOptional="Optional" />
<Input
id="secret"
type={showSecretValue ? 'text' : 'password'}
label="Secret value"
actions={
<div className="mr-1">
<Button
type="default"
icon={showSecretValue ? <EyeOff /> : <Eye />}
onClick={() => setShowSecretValue(!showSecretValue)}
/>
</div>
}
/>
</Modal.Content>
<Modal.Separator />
<Modal.Content className="space-y-4">
<EncryptionKeySelector
nameId="keyName"
label="Select a key to encrypt your secret with"
labelOptional="Optional"
selectedKeyId={selectedKeyId}
onSelectKey={setSelectedKeyId}
/>
<InformationBox
icon={<HelpCircle size={18} strokeWidth={2} />}
url="https://github.com/supabase/vault"
urlLabel="Vault documentation"
title="What is a key?"
description={
<div className="space-y-2">
<p>
Keys are used to encrypt data inside your database, and every secret in
the Vault is encrypted with a key.
</p>
<p>
You may create different keys for different purposes, such as one for
encrypting user data, and another for application data.
</p>
</div>
}
/>
</Modal.Content>
<Modal.Separator />
<Modal.Content className="flex items-center justify-end space-x-2">
<Button type="default" disabled={isSubmitting} onClick={onClose}>
Cancel
</Button>
<Button htmlType="submit" disabled={isSubmitting} loading={isSubmitting}>
Add secret
</Button>
</Modal.Content>
</>
)
}}
</Form>
)}
</Modal>
)
}
@@ -55,7 +55,7 @@ const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => {
payload.description = values.description
if (selectedKeyId !== selectedSecret?.key_id) {
let encryptionKeyId = selectedKeyId
if (values.keyId === 'create-new') {
if (encryptionKeyId === 'create-new') {
const addKeyRes = await addKeyMutation({
projectRef: project?.ref!,
connectionString: project?.connectionString,
@@ -122,7 +122,7 @@ const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => {
id: selectedSecret?.id!,
connectionString: project?.connectionString,
},
{ enabled: !!(project?.ref && selectedSecret?.id) }
{ enabled: selectedSecret !== undefined && !!(project?.ref && selectedSecret?.id) }
)
// [Joshen] JFYI this is breaking rules of hooks, will be fixed once we move to
@@ -164,7 +164,6 @@ const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => {
<Modal.Separator />
<Modal.Content className="space-y-4">
<EncryptionKeySelector
id="keyId"
nameId="keyName"
label="Select a key to encrypt your secret with"
labelOptional="Optional"
@@ -109,7 +109,7 @@ const SecretRow = ({ secret, onSelectEdit, onSelectRemove }: SecretRowProps) =>
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="end" className="w-32">
<Tooltip>
<TooltipTrigger>
<TooltipTrigger asChild>
<DropdownMenuItem
className="space-x-2"
disabled={!canManageSecrets}
@@ -127,7 +127,7 @@ const SecretRow = ({ secret, onSelectEdit, onSelectRemove }: SecretRowProps) =>
</Tooltip>
<Tooltip>
<TooltipTrigger>
<TooltipTrigger asChild>
<DropdownMenuItem
className="space-x-2"
disabled={!canManageSecrets}
@@ -10,7 +10,10 @@ export interface MembersExceedLimitModalProps {
const MembersExceedLimitModal = ({ visible, onClose }: MembersExceedLimitModalProps) => {
const selectedOrganization = useSelectedOrganization()
const slug = selectedOrganization?.slug
const { data: membersExceededLimit } = useFreeProjectLimitCheckQuery({ slug })
const { data: membersExceededLimit } = useFreeProjectLimitCheckQuery(
{ slug },
{ enabled: visible }
)
return (
<Modal
@@ -35,20 +35,24 @@ const SidePanelGitHubRepoLinker = ({ projectRef }: SidePanelGitHubRepoLinkerProp
const selectedOrganization = useSelectedOrganization()
const sidePanelStateSnapshot = useSidePanelsStateSnapshot()
const visible = sidePanelStateSnapshot.githubConnectionsOpen
const { data: gitHubAuthorization, isLoading: isLoadingGitHubAuthorization } =
useGitHubAuthorizationQuery({ enabled: sidePanelStateSnapshot.githubConnectionsOpen })
useGitHubAuthorizationQuery({ enabled: visible })
// [Alaister]: temp override with <any> until the typegen is fixed
const { data: githubReposData, isLoading: isLoadingGitHubRepos } = useGitHubRepositoriesQuery<
any[]
>({
enabled: Boolean(gitHubAuthorization),
enabled: visible && Boolean(gitHubAuthorization),
})
/**
* Supabase projects available
*/
const { data: supabaseProjectsData, isLoading: isLoadingSupabaseProjects } = useProjectsQuery()
const { data: supabaseProjectsData, isLoading: isLoadingSupabaseProjects } = useProjectsQuery({
enabled: visible,
})
const supabaseProjects = useMemo(
() =>
@@ -68,9 +72,14 @@ const SidePanelGitHubRepoLinker = ({ projectRef }: SidePanelGitHubRepoLinkerProp
[githubReposData]
)
const { data: connections } = useGitHubConnectionsQuery({
organizationId: selectedOrganization?.id,
})
const { data: connections } = useGitHubConnectionsQuery(
{
organizationId: selectedOrganization?.id,
},
{
enabled: visible,
}
)
const { mutate: createConnection, isLoading: isCreatingConnection } =
useGitHubConnectionCreateMutation({
@@ -116,7 +125,7 @@ const SidePanelGitHubRepoLinker = ({ projectRef }: SidePanelGitHubRepoLinkerProp
<SidePanel
header={'Add GitHub repository'}
size="large"
visible={sidePanelStateSnapshot.githubConnectionsOpen}
visible={visible}
hideFooter
onCancel={() => sidePanelStateSnapshot.setGithubConnectionsOpen(false)}
>
@@ -35,7 +35,10 @@ const RenameQueryModal = ({
const organization = useSelectedOrganization()
const snapV2 = useSqlEditorV2StateSnapshot()
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const { data: subscription } = useOrgSubscriptionQuery(
{ orgSlug: organization?.slug },
{ enabled: visible }
)
const isSQLSnippet = snippet.type === 'sql'
const isSQLEditorTabsEnabled = useIsSQLEditorTabsEnabled()
@@ -57,9 +57,12 @@ const ComputeInstanceSidePanel = () => {
const { panel, setPanel, closePanel } = useAddonsPagePanel()
const visible = panel === 'computeInstance'
const { data: databases } = useReadReplicasQuery({ projectRef })
const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef })
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const { data: databases } = useReadReplicasQuery({ projectRef }, { enabled: visible })
const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef }, { enabled: visible })
const { data: subscription } = useOrgSubscriptionQuery(
{ orgSlug: organization?.slug },
{ enabled: visible }
)
const { mutate: updateAddon, isLoading: isUpdating } = useProjectAddonUpdateMutation({
onSuccess: () => {
toast.success(
@@ -41,7 +41,7 @@ const DiskSizeConfigurationModal = ({
const organization = useSelectedOrganization()
const { data: projectSubscriptionData, isLoading: isLoadingSubscription } =
useOrgSubscriptionQuery({ orgSlug: organization?.slug })
useOrgSubscriptionQuery({ orgSlug: organization?.slug }, { enabled: visible })
const isLoading = isLoadingProject || isLoadingSubscription
@@ -30,7 +30,7 @@ const AddRestrictionModal = ({
const formId = 'add-restriction-form'
const { ref } = useParams()
const { data } = useNetworkRestrictionsQuery({ projectRef: ref })
const { data } = useNetworkRestrictionsQuery({ projectRef: ref }, { enabled: type !== undefined })
const ipv4Restrictions = data?.config?.dbAllowedCidrs ?? []
// @ts-ignore [Joshen] API typing issue
const ipv6Restrictions = data?.config?.dbAllowedCidrsV6 ?? []
@@ -19,7 +19,7 @@ const RemoveRestrictionModal = ({
}: RemoveRestrictionModalProps) => {
const { ref } = useParams()
const { data } = useNetworkRestrictionsQuery({ projectRef: ref })
const { data } = useNetworkRestrictionsQuery({ projectRef: ref }, { enabled: visible })
const ipv4Restrictions = data?.config?.dbAllowedCidrs ?? []
// @ts-ignore [Joshen] API typing issue
const ipv6Restrictions: string[] = data?.config?.dbAllowedCidrsV6 ?? []
@@ -18,12 +18,13 @@ const TransferProjectButton = () => {
const project = useSelectedProject()
const projectRef = project?.ref
const projectOrgId = project?.organization_id
const { data: allOrganizations } = useOrganizationsQuery()
const [isOpen, setIsOpen] = useState(false)
const { data: allOrganizations } = useOrganizationsQuery({ enabled: isOpen })
const disableProjectTransfer = useFlag('disableProjectTransfer')
const organizations = (allOrganizations || []).filter((it) => it.id !== projectOrgId)
const [isOpen, setIsOpen] = useState(false)
const [selectedOrg, setSelectedOrg] = useState()
const {
@@ -32,7 +32,10 @@ const CreateBucketModal = ({ visible, onClose }: CreateBucketModalProps) => {
},
})
const { data } = useProjectStorageConfigQuery({ projectRef: ref }, { enabled: IS_PLATFORM })
const { data } = useProjectStorageConfigQuery(
{ projectRef: ref },
{ enabled: IS_PLATFORM && visible }
)
const { value, unit } = convertFromBytes(data?.fileSizeLimit ?? 0)
const formattedGlobalUploadLimit = `${value} ${unit}`
@@ -30,7 +30,10 @@ const EditBucketModal = ({ visible, bucket, onClose }: EditBucketModalProps) =>
onClose()
},
})
const { data } = useProjectStorageConfigQuery({ projectRef: ref }, { enabled: IS_PLATFORM })
const { data } = useProjectStorageConfigQuery(
{ projectRef: ref },
{ enabled: IS_PLATFORM && visible }
)
const { value, unit } = convertFromBytes(data?.fileSizeLimit ?? 0)
const formattedGlobalUploadLimit = `${value} ${unit}`
@@ -41,7 +41,12 @@ const EnableBranchingModal = () => {
isLoading: isLoadingConnections,
isSuccess: isSuccessConnections,
isError: isErrorConnections,
} = useGitHubConnectionsQuery({ organizationId: selectedOrg?.id })
} = useGitHubConnectionsQuery(
{
organizationId: selectedOrg?.id,
},
{ enabled: snap.showEnableBranchingModal }
)
const project = useSelectedProject()
const hasMinimumPgVersion =