mirror of
https://github.com/supabase/supabase.git
synced 2026-09-22 13:37:53 +08:00
476d4a5851
## What kind of change does this PR introduce? Mechanical cleanup on top of the Button default-variant change (#50160). ## What is the current behavior? Many callsites still pass `variant="default"` even though that is now the component default. ## What is the new behavior? Removes redundant static `variant="default"` from legacy `Button` and `ButtonTooltip` callsites. Keeps explicit defaults where they document the API: - `button-default.tsx` and `button-sizes.tsx` demos - `DocsButton`, which pins neutral styling at the wrapper boundary ## To test Studio: - [Auth → Rate Limits](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/auth/rate-limits): dirty the form so Cancel appears; Cancel stays neutral, Save stays green - [Project Settings → API Keys](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/settings/api-keys): `DocsButton` in the header actions stays neutral Design system: - [Design system → Button](https://design-system-git-dnywh-dc924ac1-supabase.vercel.app/design-system/docs/components/button): `button-default` / `button-sizes` still show explicit default styling; Primary (green) is restricted to the Primary section (and `asChild`) WWW: - [www → Brand assets](https://zone-www-dot-com-git-dnywh-dc924ac1-supabase.vercel.app/brand-assets): Download logo kit / Download button kit stay neutral
228 lines
7.3 KiB
TypeScript
228 lines
7.3 KiB
TypeScript
import { useParams } from 'common'
|
||
import { isEmpty } from 'lodash'
|
||
import Link from 'next/link'
|
||
import { useCallback, useState } from 'react'
|
||
import { toast } from 'sonner'
|
||
import { Button, Card, CardContent } from 'ui'
|
||
import { Admonition } from 'ui-patterns/Admonition'
|
||
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
||
|
||
import { usePoliciesData } from './PoliciesDataContext'
|
||
import {
|
||
PolicyTableRow,
|
||
PolicyTableRowProps,
|
||
} from '@/components/interfaces/Database/Policies/PolicyTableRow'
|
||
import type { Policy } from '@/components/interfaces/Database/Policies/PolicyTableRow/PolicyTableRow.utils'
|
||
import { ProtectedSchemaWarning } from '@/components/interfaces/Database/ProtectedSchemaWarning'
|
||
import { RLSToggleDialog } from '@/components/interfaces/Database/RLSToggleDialog'
|
||
import { InlineLink } from '@/components/ui/InlineLink'
|
||
import { NoSearchResults } from '@/components/ui/NoSearchResults'
|
||
import { useDatabasePolicyDeleteMutation } from '@/data/database-policies/database-policy-delete-mutation'
|
||
import { useTableUpdateMutation } from '@/data/tables/table-update-mutation'
|
||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||
|
||
interface PoliciesProps {
|
||
search?: string
|
||
schema: string
|
||
tables: PolicyTableRowProps['table'][]
|
||
hasTables: boolean
|
||
isLocked: boolean
|
||
visibleTableIds: Set<number>
|
||
onSelectCreatePolicy: (table: string) => void
|
||
onSelectEditPolicy: (policy: Policy) => void
|
||
onResetSearch?: () => void
|
||
}
|
||
|
||
export const Policies = ({
|
||
search,
|
||
schema,
|
||
tables,
|
||
hasTables,
|
||
isLocked,
|
||
visibleTableIds,
|
||
onSelectCreatePolicy,
|
||
onSelectEditPolicy: onSelectEditPolicyAI,
|
||
onResetSearch,
|
||
}: PoliciesProps) => {
|
||
const { ref } = useParams()
|
||
const { data: project } = useSelectedProjectQuery()
|
||
const { exposedSchemas } = usePoliciesData()
|
||
|
||
const isSchemaExposedAPI = exposedSchemas.has(schema)
|
||
|
||
const [selectedTableToToggleRLS, setSelectedTableToToggleRLS] = useState<{
|
||
id: number
|
||
schema: string
|
||
name: string
|
||
rls_enabled: boolean
|
||
}>()
|
||
const [selectedPolicyToDelete, setSelectedPolicyToDelete] = useState<any>({})
|
||
|
||
const { mutateAsync: updateTable, isPending: isUpdatingTable } = useTableUpdateMutation({
|
||
onError: (error) => {
|
||
toast.error(`Failed to toggle RLS: ${error.message}`)
|
||
},
|
||
})
|
||
|
||
const { mutate: deleteDatabasePolicy, isPending: isDeletingPolicy } =
|
||
useDatabasePolicyDeleteMutation({
|
||
onSuccess: () => {
|
||
toast.success('Successfully deleted policy!')
|
||
},
|
||
onSettled: () => {
|
||
closeConfirmModal()
|
||
},
|
||
})
|
||
|
||
const closeConfirmModal = useCallback(() => {
|
||
setSelectedPolicyToDelete({})
|
||
setSelectedTableToToggleRLS(undefined)
|
||
}, [])
|
||
|
||
const onSelectToggleRLS = useCallback(
|
||
(table: { id: number; schema: string; name: string; rls_enabled: boolean }) => {
|
||
setSelectedTableToToggleRLS(table)
|
||
},
|
||
[]
|
||
)
|
||
|
||
const onSelectEditPolicy = useCallback(
|
||
(policy: Policy) => {
|
||
onSelectEditPolicyAI(policy)
|
||
},
|
||
[onSelectEditPolicyAI]
|
||
)
|
||
|
||
const onSelectDeletePolicy = useCallback((policy: Policy) => {
|
||
setSelectedPolicyToDelete(policy)
|
||
}, [])
|
||
|
||
// Methods that involve some API
|
||
const onToggleRLS = async () => {
|
||
if (!selectedTableToToggleRLS) return console.error('Table is required')
|
||
|
||
const payload = {
|
||
id: selectedTableToToggleRLS.id,
|
||
rls_enabled: !selectedTableToToggleRLS.rls_enabled,
|
||
}
|
||
|
||
return updateTable({
|
||
projectRef: project?.ref!,
|
||
connectionString: project?.connectionString,
|
||
id: selectedTableToToggleRLS.id,
|
||
name: selectedTableToToggleRLS.name,
|
||
schema: selectedTableToToggleRLS.schema,
|
||
payload: payload,
|
||
})
|
||
}
|
||
|
||
const onDeletePolicy = async () => {
|
||
if (!project) return console.error('Project is required')
|
||
deleteDatabasePolicy({
|
||
projectRef: project.ref,
|
||
connectionString: project.connectionString,
|
||
originalPolicy: selectedPolicyToDelete,
|
||
})
|
||
}
|
||
|
||
const handleCreatePolicy = useCallback(
|
||
(tableData: PolicyTableRowProps['table']) => {
|
||
onSelectCreatePolicy(tableData.name)
|
||
},
|
||
[onSelectCreatePolicy]
|
||
)
|
||
|
||
if (!hasTables) {
|
||
return (
|
||
<Card className="w-full bg-transparent">
|
||
<CardContent className="flex flex-col items-center justify-center p-8">
|
||
<h2 className="heading-default">No tables to create policies for</h2>
|
||
|
||
<p className="text-sm text-foreground-light text-center mb-4">
|
||
RLS Policies control per-user access to table rows. Create a table in this schema first
|
||
before creating a policy.
|
||
</p>
|
||
<Button asChild>
|
||
<Link href={`/project/${ref}/editor`}>Create a table</Link>
|
||
</Button>
|
||
</CardContent>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<div className="flex flex-col gap-y-4 pb-4">
|
||
{isLocked && <ProtectedSchemaWarning schema={schema} entity="policies" />}
|
||
|
||
{!isSchemaExposedAPI && (
|
||
<Admonition
|
||
type="warning"
|
||
title="No data from any table in this schema will be selectable via Supabase APIs"
|
||
>
|
||
This schema is not exposed via the Supabase APIs. You may configure this in your
|
||
project’s{' '}
|
||
<InlineLink href={`/project/${ref}/integrations/data_api/settings`}>
|
||
Data API settings
|
||
</InlineLink>
|
||
.
|
||
</Admonition>
|
||
)}
|
||
|
||
{tables.length > 0 ? (
|
||
<>
|
||
{tables.map((table) => {
|
||
const isVisible = visibleTableIds.has(table.id)
|
||
return (
|
||
<section
|
||
key={table.id}
|
||
hidden={!isVisible}
|
||
aria-hidden={!isVisible}
|
||
data-testid={`policy-table-${table.name}`}
|
||
>
|
||
<PolicyTableRow
|
||
table={table}
|
||
isLocked={schema === 'realtime' ? true : isLocked}
|
||
onSelectToggleRLS={onSelectToggleRLS}
|
||
onSelectCreatePolicy={handleCreatePolicy}
|
||
onSelectEditPolicy={onSelectEditPolicy}
|
||
onSelectDeletePolicy={onSelectDeletePolicy}
|
||
/>
|
||
</section>
|
||
)
|
||
})}
|
||
{!!search && visibleTableIds.size === 0 && (
|
||
<NoSearchResults searchString={search ?? ''} onResetFilter={onResetSearch} />
|
||
)}
|
||
</>
|
||
) : hasTables ? (
|
||
<NoSearchResults searchString={search ?? ''} onResetFilter={onResetSearch} />
|
||
) : null}
|
||
</div>
|
||
|
||
<ConfirmationModal
|
||
visible={!isEmpty(selectedPolicyToDelete)}
|
||
variant="destructive"
|
||
title="Delete policy"
|
||
description={`Are you sure you want to delete the policy “${selectedPolicyToDelete.name}”? This action cannot be undone.`}
|
||
confirmLabel="Delete"
|
||
confirmLabelLoading="Deleting"
|
||
loading={isDeletingPolicy}
|
||
onCancel={closeConfirmModal}
|
||
onConfirm={onDeletePolicy}
|
||
/>
|
||
|
||
<RLSToggleDialog
|
||
open={selectedTableToToggleRLS !== undefined}
|
||
tableName={selectedTableToToggleRLS?.name}
|
||
isEnabled={selectedTableToToggleRLS?.rls_enabled ?? false}
|
||
isSubmitting={isUpdatingTable}
|
||
onOpenChange={(open) => {
|
||
if (!open) closeConfirmModal()
|
||
}}
|
||
onConfirm={onToggleRLS}
|
||
/>
|
||
</>
|
||
)
|
||
}
|