Files
supabase/apps/studio/data/projects/project-delete-mutation.ts
Ivan Vasilov 0bcd2e196d fix: invalidate free project limit checks for all organizations when a project is deleted (#47569)
How to test:
1. Have multiple free orgs
2. Open the "create new project" page in those orgs (no need to create a
project), notice the `/members/reached-free-project-limit'` API call
3. Delete any project in any of the orgs you have
4. Go to any of orgs and open the "create new project" page, the request
should be sent again (it was purged from the cache)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved project deletion behavior so free project limit checks are
refreshed across all organizations, reducing the chance of stale
availability data.
* Continued to refresh the deleted project’s details and, when
applicable, the project list and organization details after deletion.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-16 16:05:48 +02:00

75 lines
2.4 KiB
TypeScript

import { useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { projectKeys } from './keys'
import { useInvalidateProjectsInfiniteQuery } from './org-projects-infinite-query'
import { del, handleError } from '@/data/fetchers'
import { organizationKeys } from '@/data/organizations/keys'
import type { ResponseError, UseCustomMutationOptions } from '@/types'
export type ProjectDeleteVariables = {
projectRef: string
organizationSlug?: string
}
export async function deleteProject({ projectRef }: ProjectDeleteVariables) {
const { data, error } = await del('/platform/projects/{ref}', {
params: { path: { ref: projectRef } },
})
if (error) handleError(error)
return data
}
type ProjectDeleteData = Awaited<ReturnType<typeof deleteProject>>
export const useProjectDeleteMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseCustomMutationOptions<ProjectDeleteData, ResponseError, ProjectDeleteVariables>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
const { invalidateProjectsQuery } = useInvalidateProjectsInfiniteQuery()
return useMutation<ProjectDeleteData, ResponseError, ProjectDeleteVariables>({
mutationFn: (vars) => deleteProject(vars),
async onSuccess(data, variables, context) {
// Free project limits are shared across all of a user's orgs, so clear the cached
// check for every org, not just the one the deleted project belonged to.
const freeProjectLimitCheckQueries = queryClient
.getQueryCache()
.findAll({ queryKey: ['organizations'] })
.filter((query) => query.queryKey[2] === 'free-project-limit-check')
await Promise.all([
queryClient.invalidateQueries({ queryKey: projectKeys.detail(data.ref) }),
...freeProjectLimitCheckQueries.map((query) =>
queryClient.invalidateQueries({ queryKey: query.queryKey })
),
])
if (variables.organizationSlug) {
await Promise.all([
invalidateProjectsQuery(),
queryClient.invalidateQueries({
queryKey: organizationKeys.detail(variables.organizationSlug),
}),
])
}
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to delete project: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}