perf: use subscription plan from org response (#34993)

This commit is contained in:
Kevin Grüneberg authored and GitHub committed 2025-04-14 20:37:19 +08:00
1 parent 82ee7403d8
commit d05bbb669f
55 files changed
+193 -785

No files matched your search

@@ -11,7 +11,6 @@ import NoPermission from 'components/ui/NoPermission'
import UpgradeToPro from 'components/ui/UpgradeToPro'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'
import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { IS_PLATFORM } from 'lib/constants'
@@ -57,16 +56,10 @@ export const AdvancedAuthSettingsForm = () => {
isError,
} = useAuthConfigQuery({ projectRef })
const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({
orgSlug: organization?.slug,
})
const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation()
const isTeamsEnterprisePlan =
isSuccessSubscription && subscription?.plan.id !== 'free' && subscription?.plan.id !== 'pro'
const promptTeamsEnterpriseUpgrade =
IS_PLATFORM && isSuccessSubscription && !isTeamsEnterprisePlan
const isTeamsEnterprisePlan = organization?.plan.id !== 'free' && organization?.plan.id !== 'pro'
const promptTeamsEnterpriseUpgrade = IS_PLATFORM && !isTeamsEnterprisePlan
const requestDurationForm = useForm({
resolver: zodResolver(
@@ -4,7 +4,6 @@ import { ChevronDown } from 'lucide-react'
import { useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { IS_PLATFORM } from 'lib/constants'
@@ -32,10 +31,6 @@ export const AddHookDropdown = ({
const { ref: projectRef } = useParams()
const organization = useSelectedOrganization()
const { data: subscription } = useOrgSubscriptionQuery(
{ orgSlug: organization?.slug },
{ enabled: IS_PLATFORM }
)
const { data: authConfig } = useAuthConfigQuery({ projectRef })
const canUpdateAuthHook = useCheckPermissions(PermissionAction.AUTH_EXECUTE, '*')
@@ -54,7 +49,7 @@ export const AddHookDropdown = ({
const enterpriseHookOptions = hooks.filter((h) => !isValidHook(h) && h.enterprise)
const isTeamsOrEnterprisePlan =
subscription?.plan.id === 'team' || subscription?.plan.id === 'enterprise'
organization?.plan.id === 'team' || organization?.plan.id === 'enterprise'
if (!canUpdateAuthHook) {
return (
@@ -11,7 +11,6 @@ import NoPermission from 'components/ui/NoPermission'
import UpgradeToPro from 'components/ui/UpgradeToPro'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'
import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { IS_PLATFORM } from 'lib/constants'
@@ -81,12 +80,7 @@ const phoneSchema = object({
const MfaAuthSettingsForm = () => {
const { ref: projectRef } = useParams()
const {
data: authConfig,
error: authConfigError,
isLoading,
isError,
} = useAuthConfigQuery({ projectRef })
const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef })
const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation()
// Separate loading states for each form
@@ -97,18 +91,9 @@ const MfaAuthSettingsForm = () => {
const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue')
const organization = useSelectedOrganization()
const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({
orgSlug: organization?.slug,
})
const isProPlanAndUp = isSuccessSubscription && subscription?.plan?.id !== 'free'
const isProPlanAndUp = organization?.plan?.id !== 'free'
const promptProPlanUpgrade = IS_PLATFORM && !isProPlanAndUp
const projectAddons = subscription?.project_addons.find((addon) => addon.ref === projectRef)
const hasPurchasedAuthMFAAddOn = projectAddons?.addons.some(
(addon) => addon.type === 'auth_mfa_phone'
)
// For now, we support Twilio and Vonage. Twilio Verify is not supported and the remaining providers are community maintained.
const sendSMSHookIsEnabled =
authConfig?.HOOK_SEND_SMS_URI !== null && authConfig?.HOOK_SEND_SMS_ENABLED === true
@@ -11,7 +11,6 @@ import NoPermission from 'components/ui/NoPermission'
import UpgradeToPro from 'components/ui/UpgradeToPro'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'
import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { IS_PLATFORM } from 'lib/constants'
@@ -73,11 +72,7 @@ const SessionsAuthSettingsForm = () => {
const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue')
const organization = useSelectedOrganization()
const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({
orgSlug: organization?.slug,
})
const isProPlanAndUp = isSuccessSubscription && subscription?.plan?.id !== 'free'
const isProPlanAndUp = organization?.plan?.id !== 'free'
const promptProPlanUpgrade = IS_PLATFORM && !isProPlanAndUp
const refreshTokenForm = useForm<z.infer<typeof RefreshTokenSchema>>({
@@ -1,5 +1,4 @@
import { useProjectsQuery } from 'data/projects/projects-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import type { Organization } from 'types'
import { Admonition } from 'ui-patterns'
import Link from 'next/link'
@@ -13,20 +12,18 @@ export const NoProjectsOnPaidOrgInfo = ({ organization }: NoProjectsOnPaidOrgInf
const projectCount =
allProjects?.filter((project) => project.organization_id === organization?.id).length ?? 0
const { data: orgSubscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
if (
projectCount > 0 ||
orgSubscription?.plan === undefined ||
orgSubscription.plan.id === 'free' ||
orgSubscription.plan.id === 'enterprise'
organization?.plan === undefined ||
organization.plan.id === 'free' ||
organization.plan.id === 'enterprise'
)
return null
return (
<Admonition
type="default"
title={`Your organization is on the ${orgSubscription.plan.name} plan with no projects running`}
title={`Your organization is on the ${organization.plan.name} plan with no projects running`}
description={
<div className="!max-w-full prose text-sm">
The monthly fees for the paid plan still apply. To cancel your subscription, head over to
@@ -10,7 +10,6 @@ import { pluckObjectFields } from 'lib/helpers'
import { cn } from 'ui'
import type { projectKeys } from './Connect.types'
import { getConnectionStrings as getConnectionStringsV2 } from './DatabaseSettings.utils'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { getAddons } from '../Billing/Subscription/Subscription.utils'
@@ -31,8 +30,7 @@ const ConnectTabContent = forwardRef<HTMLDivElement, ConnectContentTabProps>(
({ projectKeys, filePath, ...props }, ref) => {
const { ref: projectRef } = useParams()
const selectedOrg = useSelectedOrganization()
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: selectedOrg?.slug })
const allowPgBouncerSelection = useMemo(() => subscription?.plan.id !== 'free', [subscription])
const allowPgBouncerSelection = useMemo(() => selectedOrg?.plan.id !== 'free', [selectedOrg])
const { data: settings } = useProjectSettingsV2Query({ projectRef })
const { data: pgbouncerConfig } = usePgbouncerConfigQuery({ projectRef })
@@ -9,7 +9,6 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { usePgbouncerConfigQuery } from 'data/database/pgbouncer-config-query'
import { useSupavisorConfigurationQuery } from 'data/database/supavisor-configuration-query'
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
@@ -67,10 +66,9 @@ export const DatabaseConnectionString = () => {
const [selectedTab, setSelectedTab] = useState<DatabaseConnectionType>('uri')
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug })
const sharedPoolerPreferred = useMemo(() => {
return subscription?.plan?.id === 'free'
}, [subscription])
return org?.plan?.id === 'free'
}, [org])
const {
data: pgbouncerConfig,
@@ -1,7 +1,6 @@
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import Panel from 'components/ui/Panel'
import { useCloneBackupsQuery } from 'data/projects/clone-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { Badge, Button } from 'ui'
import { TimestampInfo } from 'ui-patterns'
@@ -16,8 +15,7 @@ export const BackupsList = ({ onSelectRestore, disabled }: BackupsListProps) =>
const { project } = useProjectContext()
const organization = useSelectedOrganization()
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const isFreePlan = subscription?.plan?.id === 'free'
const isFreePlan = organization?.plan?.id === 'free'
const { data: cloneBackups } = useCloneBackupsQuery(
{ projectRef: project?.ref },
@@ -9,7 +9,6 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte
import PasswordStrengthBar from 'components/ui/PasswordStrengthBar'
import { useProjectCloneMutation } from 'data/projects/clone-mutation'
import { useCloneBackupsQuery } from 'data/projects/clone-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { passwordStrength } from 'lib/helpers'
import { generateStrongPassword } from 'lib/project'
@@ -68,8 +67,7 @@ export const CreateNewProjectDialog = ({
},
})
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const isFreePlan = subscription?.plan?.id === 'free'
const isFreePlan = organization?.plan?.id === 'free'
const { data: cloneBackups } = useCloneBackupsQuery(
{ projectRef: project?.ref },
@@ -22,7 +22,6 @@ import { useUpdateDiskAutoscaleConfigMutation } from 'data/config/disk-autoscale
import { useDiskUtilizationQuery } from 'data/config/disk-utilization-query'
import { setProjectStatus } from 'data/projects/projects-query'
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { AddonVariantId } from 'data/subscriptions/types'
@@ -132,9 +131,7 @@ export function DiskManagementForm() {
},
{ enabled: project != null && !isFlyArchitecture }
)
const { data: subscription, isSuccess: isSubscriptionSuccess } = useOrgSubscriptionQuery({
orgSlug: org?.slug,
})
const { data: diskAutoscaleConfig, isSuccess: isDiskAutoscaleConfigSuccess } =
useDiskAutoscaleCustomConfigQuery(
{ projectRef },
@@ -178,13 +175,12 @@ export function DiskManagementForm() {
isDiskAttributesSuccess &&
isDiskUtilizationSuccess &&
isReadReplicasSuccess &&
isSubscriptionSuccess &&
isDiskAutoscaleConfigSuccess &&
isCooldownSuccess
const isRequestingChanges = data?.requested_modification !== undefined
const readReplicas = (databases ?? []).filter((db) => db.identifier !== projectRef)
const isPlanUpgradeRequired = subscription?.plan.id === 'free'
const isPlanUpgradeRequired = org?.plan.id === 'free'
const { formState } = form
const usedSize = Math.round(((diskUtil?.metrics.fs_used_bytes ?? 0) / GB) * 100) / 100
@@ -193,7 +189,7 @@ export function DiskManagementForm() {
const disableIopsThroughputConfig =
RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(form.watch('computeSize')) &&
subscription?.plan.id !== 'free'
org?.plan.id !== 'free'
const isBranch = project?.parent_project_ref !== undefined
@@ -5,7 +5,6 @@ import { UseFormReturn } from 'react-hook-form'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
@@ -161,16 +160,12 @@ export const DiskManagementReviewAndSubmitDialog = ({
/**
* Queries
* */
const {
data: subscription,
// request deps in Form handle errors and loading
} = useOrgSubscriptionQuery({ orgSlug: org?.slug })
const {
data: addons,
// request deps in Form handle errors and loading
} = useProjectAddonsQuery({ projectRef: project?.ref })
const planId = subscription?.plan.id ?? 'free'
const planId = org?.plan.id ?? 'free'
const isDirty = !!Object.keys(form.formState.dirtyFields).length
const replicaTooltipText = `Price change includes primary database and ${numReplicas} replica${numReplicas > 1 ? 's' : ''}`
@@ -185,7 +180,7 @@ export const DiskManagementReviewAndSubmitDialog = ({
availableOptions,
oldComputeSize: form.formState.defaultValues?.computeSize || 'ci_micro',
newComputeSize: form.getValues('computeSize'),
plan: subscription?.plan.id ?? 'free',
plan: org?.plan.id ?? 'free',
})
const diskSizePrice = calculateDiskSizePrice({
planId: planId,
@@ -293,7 +288,7 @@ export const DiskManagementReviewAndSubmitDialog = ({
beforePrice={Number(computeSizePrice.oldPrice)}
afterPrice={Number(computeSizePrice.newPrice)}
upgradeIncluded={
subscription?.plan.id !== 'free' &&
org?.plan.id !== 'free' &&
project?.infra_compute_size === 'nano' &&
form.getValues('computeSize') === 'ci_micro'
}
@@ -2,12 +2,10 @@ import { CpuIcon, Lock, Microchip } from 'lucide-react'
import { useMemo } from 'react'
import { UseFormReturn } from 'react-hook-form'
import { components } from 'api-types'
import { useParams } from 'common'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { DocsButton } from 'components/ui/DocsButton'
import { InlineLink } from 'components/ui/InlineLink'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { getCloudProviderArchitecture } from 'lib/cloudprovider-utils'
@@ -26,11 +24,7 @@ import {
import { ComputeBadge } from 'ui-patterns'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { DiskStorageSchemaType } from '../DiskManagement.schema'
import {
ComputeInstanceAddonVariantId,
ComputeInstanceSize,
InfraInstanceSize,
} from '../DiskManagement.types'
import { ComputeInstanceAddonVariantId, InfraInstanceSize } from '../DiskManagement.types'
import {
calculateComputeSizePrice,
getAvailableComputeOptions,
@@ -61,8 +55,6 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) {
const org = useSelectedOrganization()
const { control, formState, setValue, trigger } = form
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug })
const { project, isLoading: isProjectLoading } = useProjectContext()
const {
data: addons,
@@ -90,11 +82,11 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) {
availableOptions: availableOptions,
oldComputeSize: form.formState.defaultValues?.computeSize || 'ci_micro',
newComputeSize: form.getValues('computeSize'),
plan: subscription?.plan.id ?? 'free',
plan: org?.plan.id ?? 'free',
})
const showUpgradeBadge = showMicroUpgrade(
subscription?.plan.id ?? 'free',
org?.plan.id ?? 'free',
project?.infra_compute_size ?? 'nano'
)
@@ -179,14 +171,14 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) {
const lockedMicroDueToPITR =
compute.identifier === 'ci_micro' && !!subscriptionPitr
const lockedNanoDueToPlan =
subscription?.plan.id !== 'free' &&
org?.plan.id !== 'free' &&
project?.infra_compute_size !== 'nano' &&
compute.identifier === 'ci_nano'
const lockedOption = lockedNanoDueToPlan || lockedMicroDueToPITR
const price =
subscription?.plan.id !== 'free' &&
org?.plan.id !== 'free' &&
project?.infra_compute_size === 'nano' &&
compute.identifier === 'ci_nano'
? availableOptions.find(
@@ -5,7 +5,6 @@ import { useParams } from 'common'
import { DocsButton } from 'components/ui/DocsButton'
import { useDiskAttributesQuery } from 'data/config/disk-attributes-query'
import { useDiskUtilizationQuery } from 'data/config/disk-utilization-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import dayjs from 'dayjs'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
@@ -47,13 +46,7 @@ export function DiskSizeField({
{ projectRef },
{ enabled: project && project.cloud_provider !== 'FLY' }
)
const {
data: subscription,
error: subscriptionError,
isError: isSubscriptionError,
} = useOrgSubscriptionQuery({
orgSlug: org?.slug,
})
const {
data: diskUtil,
error: diskUtilError,
@@ -65,8 +58,8 @@ export function DiskSizeField({
{ enabled: project && project.cloud_provider !== 'FLY' }
)
const error = subscriptionError || diskUtilError || diskAttributesError
const isError = isSubscriptionError || isDiskUtilizationError || isDiskAttributesError
const error = diskUtilError || diskAttributesError
const isError = isDiskUtilizationError || isDiskAttributesError
// coming up typically takes 5 minutes, and the request is cached for 5 mins
// so doing less than 10 mins to account for both
@@ -77,7 +70,7 @@ export function DiskSizeField({
const watchedStorageType = watch('storageType')
const watchedTotalSize = watch('totalSize')
const planId = subscription?.plan.id ?? 'free'
const planId = org?.plan.id ?? 'free'
const { includedDiskGB: includedDiskGBMeta } =
PLAN_DETAILS?.[planId as keyof typeof PLAN_DETAILS] ?? {}
@@ -159,7 +152,7 @@ export function DiskSizeField({
/>
<span className="text-foreground-lighter text-sm">
{includedDiskGB > 0 &&
subscription?.plan.id &&
org?.plan.id &&
`Your plan includes ${includedDiskGB} GB of disk size for ${watchedStorageType}.`}
<div className="mt-3">
@@ -1,7 +1,6 @@
import { AnimatePresence, motion } from 'framer-motion'
import Link from 'next/link'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import {
AlertDescription_Shadcn_ as AlertDescription,
@@ -16,20 +15,8 @@ export function SpendCapDisabledSection() {
const org = useSelectedOrganization()
const project = useSelectedProject()
const {
data,
// no error/isError states handled here, as a parent component handles them
// we rely on this to check if the subscription has loaded
isSuccess,
} = useOrgSubscriptionQuery({
orgSlug: org?.slug,
})
const isSpendCapEnabled =
isSuccess &&
data?.plan.id !== 'free' &&
!data?.usage_billing_enabled &&
project?.cloud_provider !== 'FLY'
org?.plan.id !== 'free' && !org?.usage_billing_enabled && project?.cloud_provider !== 'FLY'
return (
<AnimatePresence>
@@ -1,4 +1,3 @@
import { useParams } from 'common'
import { ScaffoldContainer, ScaffoldDivider } from 'components/layouts/Scaffold'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useOrgSubscriptionQuery } from '../../../../data/subscriptions/org-subscription-query'
@@ -10,6 +9,7 @@ import CreditBalance from './CreditBalance'
import PaymentMethods from './PaymentMethods/PaymentMethods'
import Subscription from './Subscription/Subscription'
import TaxID from './TaxID/TaxID'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
const BillingSettings = () => {
const {
@@ -18,8 +18,8 @@ const BillingSettings = () => {
billingCredits: isBillingCreditsEnabledOnProfileLevel,
} = useIsFeatureEnabled(['billing:account_data', 'billing:payment_methods', 'billing:credits'])
const { slug: orgSlug } = useParams()
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug })
const org = useSelectedOrganization()
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug })
const isNotOrgWithPartnerBilling = !subscription?.billing_via_partner
const billingAccountDataEnabled =
@@ -41,7 +41,7 @@ const BillingSettings = () => {
<ScaffoldDivider />
{subscription?.plan.id !== 'free' && (
{org?.plan.id !== 'free' && (
<ScaffoldContainer id="breakdown">
<BillingBreakdown />
</ScaffoldContainer>
@@ -2,7 +2,6 @@ import dayjs from 'dayjs'
import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useOrgUsageQuery } from 'data/usage/org-usage-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button } from 'ui'
@@ -12,9 +11,6 @@ import { PricingMetric } from 'data/analytics/org-daily-stats-query'
export const Restriction = () => {
const org = useSelectedOrganization()
const { data: usage, isSuccess: isSuccessOrgUsage } = useOrgUsageQuery({ orgSlug: org?.slug })
const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({
orgSlug: org?.slug,
})
const hasExceededAnyLimits = Boolean(
usage?.usages.find(
@@ -27,13 +23,13 @@ export const Restriction = () => {
)
// don't show any alerts until everything has been fetched
if (!isSuccessOrgUsage || !isSuccessSubscription || !org) {
if (!isSuccessOrgUsage || !org) {
return null
}
let shownAlert: 'exceededLimits' | 'gracePeriod' | 'gracePeriodOver' | 'restricted' | null = null
if (subscription && hasExceededAnyLimits && !org?.restriction_status) {
if (hasExceededAnyLimits && !org?.restriction_status) {
shownAlert = 'exceededLimits'
} else if (org?.restriction_status === 'grace_period') {
shownAlert = 'gracePeriod'
@@ -59,7 +55,7 @@ export const Restriction = () => {
<AlertDescription_Shadcn_>
<p>
Your projects can become unresponsive or enter read-only mode.{' '}
{subscription.plan.id === 'free'
{org.plan.id === 'free'
? 'Please upgrade to the Pro Plan to ensure that your projects remain available.'
: 'Please disable spend cap to ensure that your projects remain available.'}
</p>
@@ -67,10 +63,10 @@ export const Restriction = () => {
<Button key="upgrade-button" asChild type="default">
<Link
href={`/org/${org?.slug}/billing?panel=${
subscription.plan.id === 'free' ? 'subscriptionPlan' : 'costControl'
org.plan.id === 'free' ? 'subscriptionPlan' : 'costControl'
}`}
>
{subscription.plan.id === 'free' ? 'Upgrade plan' : 'Change spend cap'}
{org.plan.id === 'free' ? 'Upgrade plan' : 'Change spend cap'}
</Link>
</Button>
<Button asChild type="default" icon={<ExternalLink />}>
@@ -1,6 +1,5 @@
import { Check } from 'lucide-react'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { PricingInformation } from 'shared-data'
@@ -18,8 +17,7 @@ export const EnterpriseCard = ({ plan, isCurrentPlan, billingPartner }: Enterpri
const orgSlug = selectedOrganization?.slug
const features = pickFeatures(plan, billingPartner)
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug })
const currentPlan = subscription?.plan.name
const currentPlan = selectedOrganization?.plan.name
const { mutate: sendEvent } = useSendEventMutation()
@@ -1,7 +1,5 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import dayjs from 'dayjs'
import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
import {
@@ -12,7 +10,6 @@ import {
import AlertError from 'components/ui/AlertError'
import NoPermission from 'components/ui/NoPermission'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import SparkBar from 'components/ui/SparkBar'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useFlag } from 'hooks/ui/useFlag'
@@ -4,36 +4,27 @@ import Link from 'next/link'
import { useState } from 'react'
import { toast } from 'sonner'
import { useParams } from 'common'
import {
ScaffoldSection,
ScaffoldSectionContent,
ScaffoldSectionDetail,
} from 'components/layouts/Scaffold'
import AlertError from 'components/ui/AlertError'
import NoPermission from 'components/ui/NoPermission'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { getDocument } from 'data/documents/document-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { Button } from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
const SOC2 = () => {
const { slug } = useParams()
const organization = useSelectedOrganization()
const slug = organization?.slug
const canReadSubscriptions = useCheckPermissions(
PermissionAction.BILLING_READ,
'stripe.subscriptions'
)
const {
data: subscription,
error,
isLoading,
isError,
isSuccess,
} = useOrgSubscriptionQuery({ orgSlug: slug }, { enabled: canReadSubscriptions })
const currentPlan = subscription?.plan
const currentPlan = organization?.plan
const [isOpen, setIsOpen] = useState(false)
@@ -61,31 +52,17 @@ const SOC2 = () => {
{!canReadSubscriptions ? (
<NoPermission resourceText="access our SOC2 Type 2 report" />
) : (
<>
{isLoading && (
<div className="space-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
</div>
<div className="flex items-center justify-center h-full">
{currentPlan?.id === 'free' || currentPlan?.id === 'pro' ? (
<Link href={`/org/${slug}/billing?panel=subscriptionPlan&source=soc2`}>
<Button type="default">Upgrade to Team</Button>
</Link>
) : (
<Button type="default" icon={<Download />} onClick={() => setIsOpen(true)}>
Download SOC2 Type 2 Report
</Button>
)}
{isError && <AlertError subject="Failed to retrieve subscription" error={error} />}
{isSuccess && (
<div className="flex items-center justify-center h-full">
{currentPlan?.id === 'free' || currentPlan?.id === 'pro' ? (
<Link href={`/org/${slug}/billing?panel=subscriptionPlan&source=soc2`}>
<Button type="default">Upgrade to Team</Button>
</Link>
) : (
<Button type="default" icon={<Download />} onClick={() => setIsOpen(true)}>
Download SOC2 Type 2 Report
</Button>
)}
</div>
)}
</>
</div>
)}
<ConfirmationModal
visible={isOpen}
@@ -1,5 +1,4 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { Download } from 'lucide-react'
import Link from 'next/link'
import { toast } from 'sonner'
@@ -10,27 +9,20 @@ import {
ScaffoldSectionContent,
ScaffoldSectionDetail,
} from 'components/layouts/Scaffold'
import AlertError from 'components/ui/AlertError'
import NoPermission from 'components/ui/NoPermission'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { getDocument } from 'data/documents/document-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
const SecurityQuestionnaire = () => {
const { slug } = useParams()
const organization = useSelectedOrganization()
const slug = organization?.slug
const canReadSubscriptions = useCheckPermissions(
PermissionAction.BILLING_READ,
'stripe.subscriptions'
)
const {
data: subscription,
error,
isLoading,
isError,
isSuccess,
} = useOrgSubscriptionQuery({ orgSlug: slug }, { enabled: canReadSubscriptions })
const currentPlan = subscription?.plan
const currentPlan = organization?.plan
const fetchQuestionnaire = async (orgSlug: string) => {
try {
@@ -61,37 +53,25 @@ const SecurityQuestionnaire = () => {
<NoPermission resourceText="access our security questionnaire" />
) : (
<>
{isLoading && (
<div className="space-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
</div>
)}
{isError && <AlertError subject="Failed to retrieve subscription" error={error} />}
{isSuccess && (
<div className="flex items-center justify-center h-full">
{currentPlan?.id === 'free' || currentPlan?.id === 'pro' ? (
<Link
href={`/org/${slug}/billing?panel=subscriptionPlan&source=securityQuestionnaire`}
>
<Button type="default">Upgrade to Team</Button>
</Link>
) : (
<Button
type="default"
icon={<Download />}
onClick={() => {
if (slug) fetchQuestionnaire(slug)
}}
>
Download Questionnaire
</Button>
)}
</div>
)}
<div className="flex items-center justify-center h-full">
{currentPlan?.id === 'free' || currentPlan?.id === 'pro' ? (
<Link
href={`/org/${slug}/billing?panel=subscriptionPlan&source=securityQuestionnaire`}
>
<Button type="default">Upgrade to Team</Button>
</Link>
) : (
<Button
type="default"
icon={<Download />}
onClick={() => {
if (slug) fetchQuestionnaire(slug)
}}
>
Download Questionnaire
</Button>
)}
</div>
</>
)}
</ScaffoldSectionContent>
@@ -13,10 +13,7 @@ import { useOrganizationCreateInvitationMutation } from 'data/organization-membe
import { useOrganizationRolesV2Query } from 'data/organization-members/organization-roles-query'
import { useOrganizationMembersQuery } from 'data/organizations/organization-members-query'
import { useProjectsQuery } from 'data/projects/projects-query'
import {
useHasAccessToProjectLevelPermissions,
useOrgSubscriptionQuery,
} from 'data/subscriptions/org-subscription-query'
import { useHasAccessToProjectLevelPermissions } from 'data/subscriptions/org-subscription-query'
import {
doPermissionsCheck,
useCheckPermissions,
@@ -82,11 +79,8 @@ export const InviteMemberButton = () => {
PermissionAction.BILLING_READ,
'stripe.subscriptions'
)
const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery(
{ orgSlug: slug },
{ enabled: canReadSubscriptions }
)
const currentPlan = subscription?.plan
const currentPlan = organization?.plan
const hasAccessToProjectLevelPermissions = useHasAccessToProjectLevelPermissions(slug as string)
const userMemberData = members?.find((m) => m.gotrue_id === profile?.gotrue_id)
@@ -405,16 +399,15 @@ export const InviteMemberButton = () => {
Learn more
</Link>
</Button>
{isSuccessSubscription &&
(currentPlan?.id === 'free' || currentPlan?.id === 'pro') && (
<Button asChild type="default">
<Link
href={`/org/${slug}/billing?panel=subscriptionPlan&source=inviteMemberSSO`}
>
Upgrade to Team
</Link>
</Button>
)}
{(currentPlan?.id === 'free' || currentPlan?.id === 'pro') && (
<Button asChild type="default">
<Link
href={`/org/${slug}/billing?panel=subscriptionPlan&source=inviteMemberSSO`}
>
Upgrade to Team
</Link>
</Button>
)}
</div>
</div>
}
@@ -11,7 +11,6 @@ import {
import AlertError from 'components/ui/AlertError'
import DateRangePicker from 'components/ui/DateRangePicker'
import { DocsButton } from 'components/ui/DocsButton'
import NoPermission from 'components/ui/NoPermission'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useProjectsQuery } from 'data/projects/projects-query'
@@ -17,7 +17,6 @@ import {
REALTIME_DEFAULT_CONFIG,
useRealtimeConfigurationQuery,
} from 'data/realtime/realtime-config-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import {
@@ -30,10 +29,8 @@ import {
FormField_Shadcn_,
FormMessage_Shadcn_,
Input_Shadcn_,
Switch,
} from 'ui'
import { Admonition } from 'ui-patterns'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
const formId = 'realtime-configuration-form'
@@ -50,10 +47,8 @@ export const RealtimeSettings = () => {
const { data, error, isLoading, isSuccess, isError } = useRealtimeConfigurationQuery({
projectRef,
})
const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({
orgSlug: organization?.slug,
})
const isUsageBillingEnabled = subscription?.usage_billing_enabled
const isUsageBillingEnabled = organization?.usage_billing_enabled
const { mutate: updateRealtimeConfig, isLoading: isUpdatingConfig } =
useRealtimeConfigurationUpdateMutation({
@@ -219,7 +214,7 @@ export const RealtimeSettings = () => {
/>
</FormControl_Shadcn_>
<FormMessage_Shadcn_ />
{isSuccessSubscription && !isUsageBillingEnabled && (
{!isUsageBillingEnabled && (
<Admonition
showIcon={false}
type="default"
@@ -58,7 +58,6 @@ const Addons = () => {
const { data: settings } = useProjectSettingsV2Query({ projectRef })
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: selectedOrg?.slug })
const computeSizeChangesDisabled = useFlag('disableComputeSizeChanges')
const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
const hasHipaaAddon = subscriptionHasHipaaAddon(subscription)
@@ -4,7 +4,6 @@ import { useEffect, useState } from 'react'
import { toast } from 'sonner'
import { useParams } from 'common'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon-remove-mutation'
import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
@@ -24,7 +23,7 @@ import {
SidePanel,
cn,
} from 'ui'
import { ExternalLink, AlertCircle, AlertTriangle } from 'lucide-react'
import { ExternalLink, AlertCircle } from 'lucide-react'
const CustomDomainSidePanel = () => {
const { ref: projectRef } = useParams()
@@ -42,7 +41,6 @@ const CustomDomainSidePanel = () => {
const visible = panel === 'customDomain'
const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef })
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const { mutate: updateAddon, isLoading: isUpdating } = useProjectAddonUpdateMutation({
onSuccess: () => {
toast.success(`Successfully enabled custom domain`)
@@ -69,7 +67,7 @@ const CustomDomainSidePanel = () => {
const availableOptions =
(addons?.available_addons ?? []).find((addon) => addon.type === 'custom_domain')?.variants ?? []
const isFreePlan = subscription?.plan?.id === 'free'
const isFreePlan = organization?.plan?.id === 'free'
const hasChanges = selectedOption !== (subscriptionCDOption?.variant.identifier ?? 'cd_none')
const selectedCustomDomain = availableOptions.find(
(option) => option.identifier === selectedOption
@@ -5,7 +5,6 @@ import { toast } from 'sonner'
import { useParams } from 'common'
import { InlineLink } from 'components/ui/InlineLink'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon-remove-mutation'
import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
@@ -30,7 +29,6 @@ const IPv4SidePanel = () => {
const visible = panel === 'ipv4'
const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef })
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const { mutate: updateAddon, isLoading: isUpdating } = useProjectAddonUpdateMutation({
onSuccess: () => {
toast.success(`Successfully enabled IPv4`)
@@ -57,7 +55,7 @@ const IPv4SidePanel = () => {
const availableOptions =
(addons?.available_addons ?? []).find((addon) => addon.type === 'ipv4')?.variants ?? []
const isFreePlan = subscription?.plan?.id === 'free'
const isFreePlan = organization?.plan?.id === 'free'
const hasChanges = selectedOption !== (subscriptionIpV4Option?.variant.identifier ?? 'ipv4_none')
const selectedIPv4 = availableOptions.find((option) => option.identifier === selectedOption)
const isPgBouncerEnabled = !isFreePlan
@@ -219,12 +217,10 @@ const IPv4SidePanel = () => {
charge.
</p>
)}
{!subscription?.billing_via_partner && (
<p className="text-sm text-foreground-light">
There are no immediate charges. The addon is billed at the end of your billing
cycle based on your usage and prorated to the hour.
</p>
)}
<p className="text-sm text-foreground-light">
There are no immediate charges. The addon is billed at the end of your billing cycle
based on your usage and prorated to the hour.
</p>
</>
)}
@@ -17,7 +17,6 @@ import Panel from 'components/ui/Panel'
import { useMaxConnectionsQuery } from 'data/database/max-connections-query'
import { usePgbouncerConfigQuery } from 'data/database/pgbouncer-config-query'
import { usePgbouncerConfigurationUpdateMutation } from 'data/database/pgbouncer-config-update-mutation'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
@@ -68,11 +67,9 @@ export const ConnectionPooling = () => {
isSuccess: isSuccessPgbouncerConfig,
} = usePgbouncerConfigQuery({ projectRef })
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug })
const disablePoolModeSelection = useMemo(() => {
return subscription?.plan?.id === 'free'
}, [subscription])
return org?.plan?.id === 'free'
}, [org])
const { data: maxConnData } = useMaxConnectionsQuery({
projectRef: project?.ref,
@@ -3,7 +3,6 @@ import { AlertTriangle, ExternalLink } from 'lucide-react'
import Link from 'next/link'
import { useState } from 'react'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button } from 'ui'
@@ -15,7 +14,6 @@ export const DatabaseReadOnlyAlert = () => {
const [showConfirmationModal, setShowConfirmationModal] = useState(false)
const { data: resourceWarnings } = useResourceWarningsQuery()
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const isReadOnlyMode =
(resourceWarnings ?? [])?.find((warning) => warning.project === projectRef)
@@ -37,7 +35,7 @@ export const DatabaseReadOnlyAlert = () => {
<li>
Temporarily disable read-only mode to free up space and reduce your database size
</li>
{subscription?.plan.id === 'free' ? (
{organization?.plan.id === 'free' ? (
<li>
<Link
href={`/org/${organization?.slug}/billing?panel=subscriptionPlan&source=databaseReadOnlyAlertUpgradePlan`}
@@ -46,7 +44,7 @@ export const DatabaseReadOnlyAlert = () => {
</Link>{' '}
to increase your database size limit to 8GB.
</li>
) : subscription?.plan.id === 'pro' && subscription?.usage_billing_enabled ? (
) : organization?.plan.id === 'pro' && organization?.usage_billing_enabled ? (
<li>
<Link
href={`/org/${organization?.slug}/billing?panel=subscriptionPlan&source=databaseReadOnlyAlertSpendCap`}
@@ -1,5 +1,4 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import dayjs from 'dayjs'
import { ExternalLink, Info } from 'lucide-react'
import Link from 'next/link'
import { SetStateAction } from 'react'
@@ -14,7 +13,6 @@ import { FormHeader } from 'components/ui/Forms/FormHeader'
import Panel from 'components/ui/Panel'
import { useProjectDiskResizeMutation } from 'data/config/project-disk-resize-mutation'
import { useDatabaseSizeQuery } from 'data/database/database-size-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useUrlState } from 'hooks/ui/useUrlState'
@@ -28,13 +26,9 @@ export interface DiskSizeConfigurationProps {
const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps) => {
const { ref: projectRef } = useParams()
const { project } = useProjectContext()
const { lastDatabaseResizeAt } = project ?? {}
const organization = useSelectedOrganization()
const timeTillNextAvailableDatabaseResize =
lastDatabaseResizeAt === null ? 0 : 6 * 60 - dayjs().diff(lastDatabaseResizeAt, 'minutes')
const [{ show_increase_disk_size_modal }, setUrlParams] = useUrlState()
const showIncreaseDiskSizeModal = show_increase_disk_size_modal === 'true'
const setShowIncreaseDiskSizeModal = (value: SetStateAction<boolean>) => {
@@ -48,7 +42,6 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps)
},
})
const { data: projectSubscriptionData } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const { isLoading: isUpdatingDiskSize } = useProjectDiskResizeMutation({
onSuccess: (_, variables) => {
toast.success(`Successfully updated disk size to ${variables.volumeSize} GB`)
@@ -67,7 +60,7 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps)
return (
<div id="diskManagement">
<FormHeader title="Disk Management" />
{projectSubscriptionData?.usage_billing_enabled === true ? (
{organization?.usage_billing_enabled === true ? (
<div className="flex flex-col gap-3">
<Panel className="!m-0">
<Panel.Content>
@@ -155,12 +148,12 @@ Read more about [disk management](https://supabase.com/docs/guides/platform/data
<Alert_Shadcn_>
<InfoIcon />
<AlertTitle_Shadcn_>
{projectSubscriptionData?.plan?.id === 'free'
{organization?.plan?.id === 'free'
? 'Disk size configuration is not available for projects on the Free Plan'
: 'Disk size configuration is only available when the spend cap has been disabled'}
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
{projectSubscriptionData?.plan?.id === 'free' ? (
{organization?.plan?.id === 'free' ? (
<p>
If you are intending to use more than 500MB of disk space, then you will need to
upgrade to at least the Pro Plan.
@@ -174,13 +167,11 @@ Read more about [disk management](https://supabase.com/docs/guides/platform/data
<Button asChild type="default" className="mt-3">
<Link
href={`/org/${organization?.slug}/billing?panel=${
projectSubscriptionData?.plan?.id === 'free' ? 'subscriptionPlan' : 'costControl'
organization?.plan?.id === 'free' ? 'subscriptionPlan' : 'costControl'
}`}
target="_blank"
>
{projectSubscriptionData?.plan?.id === 'free'
? 'Upgrade subscription'
: 'Disable spend cap'}
{organization?.plan?.id === 'free' ? 'Upgrade subscription' : 'Disable spend cap'}
</Link>
</Button>
</AlertDescription_Shadcn_>
@@ -6,7 +6,6 @@ import { FormHeader } from 'components/ui/Forms/FormHeader'
import Panel from 'components/ui/Panel'
import UpgradeToPro from 'components/ui/UpgradeToPro'
import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useFlag } from 'hooks/ui/useFlag'
@@ -22,8 +21,7 @@ const CustomDomainConfig = () => {
const customDomainsDisabledDueToQuota = useFlag('customDomainsDisabledDueToQuota')
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const plan = subscription?.plan?.id
const plan = organization?.plan?.id
const { data: addons, isLoading: isLoadingAddons } = useProjectAddonsQuery({ projectRef: ref })
const hasCustomDomainAddon = !!addons?.selected_addons.find((x) => x.type === 'custom_domain')
@@ -12,7 +12,6 @@ import {
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useProjectPauseMutation } from 'data/projects/project-pause-mutation'
import { setProjectStatus } from 'data/projects/projects-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { PROJECT_STATUS } from 'lib/constants'
@@ -33,8 +32,7 @@ const PauseProjectButton = () => {
'queue_jobs.projects.pause'
)
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const isFreePlan = subscription?.plan.id === 'free'
const isFreePlan = organization?.plan.id === 'free'
const { mutate: pauseProject, isLoading: isPausing } = useProjectPauseMutation({
onSuccess: (res, variables) => {
@@ -18,7 +18,6 @@ import {
} from 'data/config/project-upgrade-eligibility-query'
import { ReleaseChannel, useProjectUpgradeMutation } from 'data/projects/project-upgrade-mutation'
import { setProjectStatus } from 'data/projects/projects-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useFlag } from 'hooks/ui/useFlag'
import { PROJECT_STATUS } from 'lib/constants'
@@ -72,8 +71,7 @@ const ProjectUpgradeAlert = () => {
const projectUpgradeDisabled = useFlag('disableProjectUpgrade')
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug })
const planId = subscription?.plan.id ?? 'free'
const planId = org?.plan.id ?? 'free'
const { data: diskAttributes } = useDiskAttributesQuery({ projectRef: ref })
const { includedDiskGB: includedDiskGBMeta } = PLAN_DETAILS[planId]
@@ -56,7 +56,7 @@ const InfrastructureActivity = () => {
const { data: subscription, isLoading: isLoadingSubscription } = useOrgSubscriptionQuery({
orgSlug: organization?.slug,
})
const isFreePlan = subscription?.plan?.id === 'free'
const isFreePlan = organization?.plan?.id === 'free'
const { data: resourceWarnings } = useResourceWarningsQuery()
const projectResourceWarnings = resourceWarnings?.find((x) => x.project === projectRef)
@@ -94,9 +94,9 @@ const InfrastructureActivity = () => {
}, [dateRange, subscription])
const upgradeUrl =
subscription === undefined
organization === undefined
? `/`
: subscription.plan.id === 'free'
: organization.plan.id === 'free'
? `/org/${organization?.slug ?? '[slug]'}/billing#subscription`
: `/project/${projectRef}/settings/addons`
@@ -112,7 +112,7 @@ const InfrastructureActivity = () => {
// LF seems to have an issue with the milliseconds, causes infinite loading sometimes
return new Date(dateRange?.period_start?.date ?? 0).toISOString().slice(0, -5) + 'Z'
}
}, [dateRange, subscription])
}, [dateRange])
const endDate = useMemo(() => {
if (dateRange?.period_end?.date === 'Invalid Date') return undefined
@@ -127,7 +127,7 @@ const InfrastructureActivity = () => {
// LF seems to have an issue with the milliseconds, causes infinite loading sometimes
return new Date(dateRange.period_end.date ?? 0).toISOString().slice(0, -5) + 'Z'
}
}, [dateRange, subscription])
}, [dateRange])
// Switch to hourly interval, if the timeframe is <48 hours
let interval: '1d' | '1h' = '1d'
@@ -23,7 +23,6 @@ import {
MAX_REPLICAS_BELOW_XL,
useReadReplicasQuery,
} from 'data/read-replicas/replicas-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
@@ -73,12 +72,11 @@ const DeployNewReplicaPanel = ({
const { data } = useReadReplicasQuery({ projectRef })
const { data: addons, isSuccess } = useProjectAddonsQuery({ projectRef })
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug })
const { data: diskConfiguration } = useDiskAttributesQuery({ projectRef })
const isNotOnTeamOrEnterprisePlan = useMemo(
() => !['team', 'enterprise'].includes(subscription?.plan.id ?? ''),
[subscription]
() => !['team', 'enterprise'].includes(org?.plan.id ?? ''),
[org]
)
const { data: allOverdueInvoices } = useOverdueInvoicesQuery({
enabled: isNotOnTeamOrEnterprisePlan,
@@ -166,14 +164,13 @@ const DeployNewReplicaPanel = ({
: MAX_REPLICAS_ABOVE_XL
const reachedMaxReplicas =
(data ?? []).filter((db) => db.identifier !== projectRef).length >= maxNumberOfReplicas
const isFreePlan = subscription?.plan.id === 'free'
const isFreePlan = org?.plan.id === 'free'
const isAWSProvider = project?.cloud_provider === 'AWS'
const isWalgEnabled = project?.is_physical_backups_enabled
const currentComputeAddon = addons?.selected_addons.find(
(addon) => addon.type === 'compute_instance'
)
const isProWithSpendCapEnabled =
subscription?.plan.id === 'pro' && !subscription.usage_billing_enabled
const isProWithSpendCapEnabled = org?.plan.id === 'pro' && !org.usage_billing_enabled
const isMinimallyOnSmallCompute =
currentComputeAddon?.variant.identifier !== undefined &&
currentComputeAddon?.variant.identifier !== 'ci_micro'
@@ -8,7 +8,6 @@ import PreviewFilterPanel from 'components/interfaces/Settings/Logs/PreviewFilte
import LoadingOpacity from 'components/ui/LoadingOpacity'
import ShimmerLine from 'components/ui/ShimmerLine'
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import useLogsPreview from 'hooks/analytics/useLogsPreview'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt'
@@ -65,7 +64,6 @@ export const LogsPreviewer = ({
const [selectedLogId, setSelectedLogId] = useSelectedLog()
const { data: databases, isSuccess } = useReadReplicasQuery({ projectRef })
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const table = !tableName ? LOGS_TABLES[queryType] : tableName
@@ -97,12 +95,12 @@ export const LogsPreviewer = ({
// Show the prompt on page load based on query params
useEffect(() => {
if (timestampStart) {
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(timestampStart, subscription?.plan?.id)
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(timestampStart, organization?.plan?.id)
if (shouldShowUpgradePrompt) {
setShowUpgradePrompt(!showUpgradePrompt)
}
}
}, [timestampStart, subscription])
}, [timestampStart, organization])
useEffect(() => {
if (db !== undefined) {
@@ -137,7 +135,7 @@ export const LogsPreviewer = ({
} else if (event === 'event-chart-bar-click') {
setTimeRange(from || '', to || '')
} else if (event === 'datepicker-change') {
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from || '', subscription?.plan?.id)
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from || '', organization?.plan?.id)
if (shouldShowUpgradePrompt) {
setShowUpgradePrompt(!showUpgradePrompt)
@@ -2,7 +2,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
import * as Sentry from '@sentry/nextjs'
import { ChevronRight, ExternalLink, Loader2, Mail, Plus, X } from 'lucide-react'
import Link from 'next/link'
import { ChangeEvent, useEffect, useRef, useState } from 'react'
import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import * as z from 'zod'
@@ -14,7 +14,6 @@ import { useSendSupportTicketMutation } from 'data/feedback/support-ticket-send'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import type { Project } from 'data/projects/project-detail-query'
import { useProjectsQuery } from 'data/projects/projects-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { detectBrowser } from 'lib/helpers'
import { useProfile } from 'lib/profile'
import {
@@ -133,14 +132,10 @@ export const SupportFormV2 = ({ setSentCategory, setSelectedProject }: SupportFo
isSuccess: isSuccessOrganizations,
} = useOrganizationsQuery()
const {
data: subscription,
isLoading: isLoadingSubscription,
isSuccess: isSuccessSubscription,
} = useOrgSubscriptionQuery({
orgSlug: organizationSlug === 'no-org' ? undefined : organizationSlug,
})
const selectedOrganization = useMemo(
() => organizations?.find((org) => org.slug === organizationSlug),
[organizationSlug, organizations]
)
const {
data: allProjects,
isLoading: isLoadingProjects,
@@ -161,7 +156,8 @@ export const SupportFormV2 = ({ setSentCategory, setSelectedProject }: SupportFo
})
const respondToEmail = profile?.primary_email ?? 'your email'
const subscriptionPlanId = subscription?.plan.id
const subscriptionPlanId = selectedOrganization?.plan.id
const projects = [
...(allProjects ?? []).filter((project) => project.organization_slug === organizationSlug),
{ ref: 'no-project', name: 'No specific project' } as Partial<Project>,
@@ -268,9 +264,8 @@ export const SupportFormV2 = ({ setSentCategory, setSelectedProject }: SupportFo
form.setValue('projectRef', selectedProject.ref)
}
} else if (slug) {
const selectedOrganization = organizations?.find((org) => org.slug === slug)
if (selectedOrganization !== undefined) {
form.setValue('organizationSlug', selectedOrganization.slug)
if (organizations.some((it) => it.slug === slug)) {
form.setValue('organizationSlug', slug)
}
} else if (ref === undefined && slug === undefined) {
const firstOrganization = organizations?.[0]
@@ -333,10 +328,7 @@ export const SupportFormV2 = ({ setSentCategory, setSelectedProject }: SupportFo
) : (
(organizations ?? []).find((o) => o.slug === field.value)?.name
)}
{organizationSlug !== 'no-org' && isLoadingSubscription && (
<Loader2 size={14} className="animate-spin" />
)}
{isSuccessSubscription && (
{subscriptionPlanId && (
<Badge variant="outline" className="capitalize">
{subscriptionPlanId}
</Badge>
@@ -8,7 +8,6 @@ import { useNewLayout } from 'components/interfaces/App/FeaturePreview/FeaturePr
import PartnerIcon from 'components/ui/PartnerIcon'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import {
@@ -40,7 +39,6 @@ const OrganizationDropdown = () => {
const slug = selectedOrganization?.slug
const orgName = selectedOrganization?.name
const { data: subscription, isSuccess } = useOrgSubscriptionQuery({ orgSlug: slug })
const [open, setOpen] = useState(false)
@@ -56,7 +54,7 @@ const OrganizationDropdown = () => {
<span className="text-foreground max-w-32 lg:max-w-none truncate hidden md:block">
{orgName}
</span>
{isSuccess && <Badge variant="default">{subscription?.plan.name}</Badge>}
<Badge variant="default">{selectedOrganization?.plan.name}</Badge>
</Link>
)}
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
@@ -71,7 +69,7 @@ const OrganizationDropdown = () => {
<Button type="text" className="pr-2" iconRight={<ChevronsUpDown />}>
<div className="flex items-center space-x-2">
<p className={'text-xs'}>{orgName}</p>
{isSuccess && <Badge variant="default">{subscription?.plan.name}</Badge>}
<Badge variant="default">{selectedOrganization?.plan.name}</Badge>
</div>
</Button>
)}
@@ -14,7 +14,6 @@ import InlineEditorButton from 'components/layouts/AppLayout/InlineEditorButton'
import OrganizationDropdown from 'components/layouts/AppLayout/OrganizationDropdown'
import ProjectDropdown from 'components/layouts/AppLayout/ProjectDropdown'
import { getResourcesExceededLimitsOrg } from 'components/ui/OveragesBanner/OveragesBanner.utils'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useOrgUsageQuery } from 'data/usage/org-usage-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
@@ -69,14 +68,10 @@ const LayoutHeader = ({
const isBranchingEnabled = selectedProject?.is_branch_enabled === true
const { setMobileMenuOpen } = useAppStateSnapshot()
const { data: subscription } = useOrgSubscriptionQuery({
orgSlug: selectedOrganization?.slug,
})
// We only want to query the org usage and check for possible over-ages for plans without usage billing enabled (free or pro with spend cap)
const { data: orgUsage } = useOrgUsageQuery(
{ orgSlug: selectedOrganization?.slug },
{ enabled: subscription?.usage_billing_enabled === false }
{ enabled: selectedOrganization?.usage_billing_enabled === false }
)
const exceedingLimits = useMemo(() => {
@@ -22,7 +22,6 @@ import { useFreeProjectLimitCheckQuery } from 'data/organizations/free-project-l
import { useProjectPauseStatusQuery } from 'data/projects/project-pause-status-query'
import { useProjectRestoreMutation } from 'data/projects/project-restore-mutation'
import { setProjectStatus } from 'data/projects/projects-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useFlag, usePHFlag } from 'hooks/ui/useFlag'
@@ -78,7 +77,6 @@ export const ProjectPausedState = ({ product }: ProjectPausedStateProps) => {
const enableProBenefitWording = usePHFlag('proBenefitWording')
const orgSlug = selectedOrganization?.slug
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug })
const {
data: pauseStatus,
error: pauseStatusError,
@@ -97,7 +95,7 @@ export const ProjectPausedState = ({ product }: ProjectPausedStateProps) => {
pauseStatus?.max_days_till_restore_disabled ??
0
const isFreePlan = subscription?.plan?.id === 'free'
const isFreePlan = selectedOrganization?.plan?.id === 'free'
const isRestoreDisabled = enforceNinetyDayUnpauseExpiry && isSuccess && !pauseStatus.can_restore
const { data: membersExceededLimit } = useFreeProjectLimitCheckQuery(
@@ -14,7 +14,6 @@ import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import UpgradeToPro from 'components/ui/UpgradeToPro'
import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query'
import { useProjectStorageConfigUpdateUpdateMutation } from 'data/config/project-storage-config-update-mutation'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import {
@@ -56,10 +55,7 @@ const StorageSettings = () => {
} = useProjectStorageConfigQuery({ projectRef })
const organization = useSelectedOrganization()
const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({
orgSlug: organization?.slug,
})
const isFreeTier = isSuccessSubscription && subscription.plan.id === 'free'
const isFreeTier = organization?.plan.id === 'free'
const [initialValues, setInitialValues] = useState<StorageSettingsState>({
fileSizeLimit: 0,
+1 -3
View File
@@ -2,7 +2,6 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import Link from 'next/link'
import { ReactNode } from 'react'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
@@ -31,8 +30,7 @@ const UpgradeToPro = ({
}: UpgradeToProProps) => {
const project = useSelectedProject()
const organization = useSelectedOrganization()
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const plan = subscription?.plan?.id
const plan = organization?.plan?.id
const canUpdateSubscription = useCheckPermissions(
PermissionAction.BILLING_WRITE,
@@ -6,6 +6,7 @@ import { toast } from 'sonner'
import type { ResponseError } from 'types/base'
import { subscriptionKeys } from './keys'
import type { SubscriptionTier } from './types'
import { organizationKeys } from 'data/organizations/keys'
export type OrgSubscriptionUpdateVariables = {
slug: string
@@ -63,6 +64,8 @@ export const useOrgSubscriptionUpdateMutation = ({
queryClient.invalidateQueries(subscriptionKeys.orgPlans(slug)),
queryClient.invalidateQueries(usageKeys.orgUsage(slug)),
queryClient.invalidateQueries(invoicesKeys.orgUpcomingPreview(slug)),
queryClient.invalidateQueries(organizationKeys.detail(slug)),
queryClient.invalidateQueries(organizationKeys.list()),
])
await onSuccess?.(data, variables, context)
+6 -10
View File
@@ -1,25 +1,21 @@
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSelectedOrganization } from './useSelectedOrganization'
export function useCurrentOrgPlan() {
const currentOrg = useSelectedOrganization()
const { data, isLoading, isSuccess } = useOrgSubscriptionQuery({
orgSlug: currentOrg?.slug,
})
if (isLoading) {
if (!currentOrg) {
return {
plan: null,
usageBillingEnabled: null,
isLoading,
isLoading: true,
isSuccess: false,
}
} else {
return {
plan: data?.plan,
usageBillingEnabled: data?.usage_billing_enabled,
isLoading,
isSuccess,
plan: currentOrg?.plan,
usageBillingEnabled: currentOrg?.usage_billing_enabled,
isLoading: false,
isSuccess: true,
}
}
}
+1 -3
View File
@@ -1,14 +1,12 @@
import { maybeShowUpgradePrompt } from 'components/interfaces/Settings/Logs/Logs.utils'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useEffect, useState } from 'react'
import { useSelectedOrganization } from './useSelectedOrganization'
export const useUpgradePrompt = (from: string) => {
const organization = useSelectedOrganization()
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const [showUpgradePrompt, setShowUpgradePrompt] = useState(false)
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from, subscription?.plan?.id)
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from, organization?.plan?.id)
useEffect(() => {
if (shouldShowUpgradePrompt) {
@@ -24,6 +24,10 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => {
name: process.env.DEFAULT_ORGANIZATION_NAME || 'Default Organization',
slug: 'default-org-slug',
billing_email: 'billing@supabase.co',
plan: {
id: 'enterprise',
name: 'Enterprise',
},
},
]
return res.status(200).json(response)
+5 -8
View File
@@ -41,7 +41,6 @@ import {
useProjectCreateMutation,
} from 'data/projects/project-create-mutation'
import { useProjectsQuery } from 'data/projects/projects-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { withAuth } from 'hooks/misc/withAuth'
@@ -161,11 +160,9 @@ const Wizard: NextPageWithLayout = () => {
}
}, [currentOrg, projectCreationExperimentGroup, router])
const { data: orgSubscription } = useOrgSubscriptionQuery({ orgSlug: slug })
const isNotOnTeamOrEnterprisePlan = useMemo(
() => !['team', 'enterprise'].includes(orgSubscription?.plan.id ?? ''),
[orgSubscription]
() => !['team', 'enterprise'].includes(currentOrg?.plan.id ?? ''),
[currentOrg]
)
const { data: allOverdueInvoices } = useOverdueInvoicesQuery({
@@ -220,7 +217,7 @@ const Wizard: NextPageWithLayout = () => {
const showNonProdFields = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod'
const freePlanWithExceedingLimits =
orgSubscription?.plan?.id === 'free' && hasMembersExceedingFreeTierLimit
currentOrg?.plan?.id === 'free' && hasMembersExceedingFreeTierLimit
const isManagedByVercel = currentOrg?.managed_by === 'vercel-marketplace'
@@ -348,7 +345,7 @@ const Wizard: NextPageWithLayout = () => {
dbPricingTierId: 'tier_free',
// only set the compute size on pro+ plans. Free plans always use micro (nano in the future) size.
dbInstanceSize:
orgSubscription?.plan.id === 'free' ? undefined : (instanceSize as DesiredInstanceSize),
currentOrg?.plan.id === 'free' ? undefined : (instanceSize as DesiredInstanceSize),
dataApiExposedSchemas: !dataApi ? [] : undefined,
dataApiUseApiSchema: !dataApi ? false : useApiSchema,
postgresEngine: useOrioleDb ? availableOrioleVersion?.postgres_engine : postgresEngine,
@@ -565,7 +562,7 @@ const Wizard: NextPageWithLayout = () => {
</Panel.Content>
)}
{orgSubscription?.plan && orgSubscription?.plan.id !== 'free' && (
{currentOrg?.plan && currentOrg?.plan.id !== 'free' && (
<Panel.Content>
<FormField_Shadcn_
control={form.control}
+5 -10
View File
@@ -46,7 +46,6 @@ import {
useProjectCreateMutation,
} from 'data/projects/project-create-mutation'
import { useProjectsQuery } from 'data/projects/projects-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { withAuth } from 'hooks/misc/withAuth'
@@ -172,12 +171,8 @@ const WizardForm = () => {
const { data: organizations, isSuccess: isOrganizationsSuccess } = useOrganizationsQuery()
const currentOrg = organizations?.find((o: any) => o.slug === slug)
const { data: orgSubscription } = useOrgSubscriptionQuery({ orgSlug: slug })
const { data: allOverdueInvoices } = useOverdueInvoicesQuery({
enabled:
orgSubscription !== undefined &&
!['team', 'enterprise'].includes(orgSubscription?.plan.id ?? ''),
enabled: currentOrg !== undefined && !['team', 'enterprise'].includes(currentOrg.plan.id ?? ''),
})
const overdueInvoices = (allOverdueInvoices ?? []).filter(
(x) => x.organization_id === currentOrg?.id
@@ -221,7 +216,7 @@ const WizardForm = () => {
const showNonProdFields = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod'
const freePlanWithExceedingLimits =
orgSubscription?.plan?.id === 'free' && hasMembersExceedingFreeTierLimit
currentOrg?.plan?.id === 'free' && hasMembersExceedingFreeTierLimit
const isManagedByVercel = currentOrg?.managed_by === 'vercel-marketplace'
@@ -342,7 +337,7 @@ const WizardForm = () => {
dbSql: sql,
// only set the compute size on pro+ plans. Free plans always use micro (nano in the future) size.
dbInstanceSize:
orgSubscription?.plan.id === 'free' ? undefined : (instanceSize as DesiredInstanceSize),
currentOrg?.plan.id === 'free' ? undefined : (instanceSize as DesiredInstanceSize),
dataApiExposedSchemas: !dataApi ? [] : undefined,
dataApiUseApiSchema: !dataApi ? false : useApiSchema,
postgresEngine: useOrioleDb ? availableOrioleVersion?.postgres_engine : postgresEngine,
@@ -782,7 +777,7 @@ const WizardForm = () => {
</Button>
)}
</div>
{orgSubscription?.plan && orgSubscription?.plan.id !== 'free' && (
{currentOrg?.plan.id !== 'free' && (
<div className="py-5 border-b">
<FormField_Shadcn_
control={form.control}
@@ -795,7 +790,7 @@ const WizardForm = () => {
</div>
}
description={
<div className="flex flex gap-2">
<div className="flex gap-2">
<Link
target="_blank"
rel="noopener noreferrer"
@@ -14,7 +14,6 @@ import NoPermission from 'components/ui/NoPermission'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import UpgradeToPro from 'components/ui/UpgradeToPro'
import { useBackupsQuery } from 'data/database/backups-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useIsOrioleDb } from 'hooks/misc/useSelectedProject'
@@ -55,9 +54,7 @@ const PITR = () => {
const isOrioleDb = useIsOrioleDb()
const { data: backups, error, isLoading, isError, isSuccess } = useBackupsQuery({ projectRef })
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const plan = subscription?.plan?.id
const plan = organization?.plan?.id
const isEnabled = backups?.pitr_enabled
const isActiveHealthy = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY
@@ -20,7 +20,6 @@ import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import UpgradeToPro from 'components/ui/UpgradeToPro'
import { useCloneBackupsQuery } from 'data/projects/clone-query'
import { useCloneStatusQuery } from 'data/projects/clone-status-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useIsOrioleDb } from 'hooks/misc/useSelectedProject'
@@ -33,7 +32,6 @@ import Panel from 'components/ui/Panel'
import { projectSpecToMonthlyPrice } from 'components/interfaces/Database/Backups/RestoreToNewProject/RestoreToNewProject.utils'
import { useDiskAttributesQuery } from 'data/config/disk-attributes-query'
import { DiskType } from 'components/interfaces/DiskManagement/ui/DiskManagement.constants'
import { InfraInstanceSize } from 'components/interfaces/DiskManagement/DiskManagement.types'
import DefaultLayout from 'components/layouts/DefaultLayout'
const RestoreToNewProjectPage: NextPageWithLayout = () => {
@@ -63,8 +61,7 @@ RestoreToNewProjectPage.getLayout = (page) => (
const RestoreToNewProject = () => {
const { project } = useProjectContext()
const organization = useSelectedOrganization()
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const isFreePlan = subscription?.plan?.id === 'free'
const isFreePlan = organization?.plan?.id === 'free'
const isOrioleDb = useIsOrioleDb()
const [refetchInterval, setRefetchInterval] = useState<number | false>(false)
@@ -80,7 +77,7 @@ const RestoreToNewProject = () => {
isError,
} = useCloneBackupsQuery({ projectRef: project?.ref }, { enabled: !isFreePlan })
const plan = subscription?.plan?.id
const plan = organization?.plan?.id
const isActiveHealthy = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY
const isPermissionsLoaded = usePermissionsLoaded()
@@ -95,7 +92,7 @@ const RestoreToNewProject = () => {
const IS_PG15_OR_ABOVE = dbVersion >= 15
const targetVolumeSizeGb = cloneBackups?.target_volume_size_gb
const targetComputeSize = cloneBackups?.target_compute_size
const planId = subscription?.plan?.id ?? 'free'
const planId = organization?.plan?.id ?? 'free'
const { data } = useDiskAttributesQuery({ projectRef: project?.ref })
const storageType = data?.attributes?.type ?? 'gp3'
@@ -45,7 +45,6 @@ import {
UpsertContentPayload,
useContentUpsertMutation,
} from 'data/content/content-upsert-mutation'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import useLogsQuery from 'hooks/analytics/useLogsQuery'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt'
@@ -95,7 +94,6 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
})
const query = content?.content.find((x) => x.id === queryId)
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const {
params,
logData,
@@ -287,7 +285,7 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
}
const handleDateChange = ({ to, from }: DatePickerToFrom) => {
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from, subscription?.plan?.id)
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from, organization?.plan?.id)
if (shouldShowUpgradePrompt) {
setShowUpgradePrompt(!showUpgradePrompt)
@@ -347,12 +345,12 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
// Show the prompt on page load based on query params
useEffect(() => {
if (its) {
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(its as string, subscription?.plan?.id)
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(its as string, organization?.plan?.id)
if (shouldShowUpgradePrompt) {
setShowUpgradePrompt(!showUpgradePrompt)
}
}
}, [its, subscription])
}, [its, organization])
return (
<div className="w-full h-full mx-auto">
@@ -15,7 +15,6 @@ import DefaultLayout from 'components/layouts/DefaultLayout'
import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout'
import ShimmerLine from 'components/ui/ShimmerLine'
import { useApiReport } from 'data/reports/api-report-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { NextPageWithLayout } from 'types'
@@ -35,8 +34,7 @@ export const ApiReport: NextPageWithLayout = () => {
refresh,
} = report
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const plan = subscription?.plan
const plan = organization?.plan
const handleDatepickerChange = ({ from, to }: DatePickerToFrom) => {
mergeParams({
@@ -12,7 +12,6 @@ import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import ShimmerLine from 'components/ui/ShimmerLine'
import { useStorageReport } from 'data/reports/storage-report-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { RefreshCw } from 'lucide-react'
import type { NextPageWithLayout } from 'types'
@@ -24,8 +23,7 @@ export const StorageReport: NextPageWithLayout = () => {
const { isLoading, refresh } = report
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
const plan = subscription?.plan
const plan = organization?.plan
const handleDatepickerChange = ({ from, to }: DatePickerToFrom) => {
report.mergeParams({
@@ -26,150 +26,6 @@ vi.mock('lib/gotrue', () => ({
auth: { onAuthStateChange: vi.fn() },
}))
// in the event that log metadata is not available, fall back to default renderer
// generate test cases for each query type
// const defaultRendererFallbacksCases = [
// 'api',
// 'database',
// 'auth',
// 'supavisor',
// 'postgrest',
// 'storage',
// 'realtime',
// ].map((queryType) => ({
// testName: 'fallback to default render',
// queryType,
// tableName: undefined,
// tableLog: logDataFixture({
// event_message: 'some message',
// metadata: undefined,
// }),
// selectionLog: logDataFixture({
// metadata: undefined,
// }),
// tableTexts: [/some message/],
// selectionTexts: [/some message/],
// }))
// test.skip.each([
// {
// queryType: 'api',
// tableName: undefined,
// tableLog: logDataFixture({
// path: 'some-path',
// method: 'POST',
// status_code: '400',
// metadata: undefined,
// }),
// selectionLog: logDataFixture({
// metadata: [{ request: [{ method: 'POST' }] }],
// }),
// tableTexts: [/POST/, /some\-path/, /400/],
// selectionTexts: [/POST/, /Timestamp/, RegExp(`${new Date().getFullYear()}.+`, 'g')],
// },
// {
// queryType: 'database',
// tableName: undefined,
// tableLog: logDataFixture({
// error_severity: 'ERROR',
// event_message: 'some db event',
// metadata: undefined,
// }),
// selectionLog: logDataFixture({
// metadata: [
// {
// parsed: [
// {
// application_type: 'client backend',
// error_severity: 'ERROR',
// hint: 'some pg hint',
// },
// ],
// },
// ],
// }),
// tableTexts: [/ERROR/, /some db event/],
// selectionTexts: [
// /client backend/,
// /some pg hint/,
// /ERROR/,
// /Timestamp/,
// RegExp(`${new Date().getFullYear()}.+`, 'g'),
// ],
// },
// {
// queryType: 'auth',
// tableName: undefined,
// tableLog: logDataFixture({
// event_message: 'some event_message',
// level: 'info',
// path: '/auth-path',
// msg: 'some metadata_msg',
// status: 300,
// metadata: undefined,
// }),
// selectionLog: logDataFixture({
// event_message: 'some event_message',
// metadata: {
// msg: 'some metadata_msg',
// path: '/auth-path',
// level: 'info',
// status: 300,
// },
// }),
// tableTexts: [/auth\-path/, /some metadata_msg/, /INFO/],
// selectionTexts: [
// /auth\-path/,
// /some metadata_msg/,
// /INFO/,
// /300/,
// /Timestamp/,
// RegExp(`${new Date().getFullYear()}.+`, 'g'),
// ],
// },
// ...defaultRendererFallbacksCases,
// // these all use teh default selection/table renderers
// ...['supavisor', 'postgrest', 'storage', 'realtime', 'supavisor'].map((queryType) => ({
// queryType,
// tableName: undefined,
// tableLog: logDataFixture({
// event_message: 'some message',
// metadata: undefined,
// }),
// selectionLog: logDataFixture({
// metadata: [{ some: [{ nested: 'value' }] }],
// }),
// tableTexts: [/some message/],
// selectionTexts: [/some/, /nested/, /value/, RegExp(`${new Date().getFullYear()}.+`, 'g')],
// })),
// ])(
// 'selection $queryType $queryType, $tableName , can display log data and metadata $testName',
// async ({ queryType, tableName, tableLog, selectionLog, tableTexts, selectionTexts }) => {
// render(<LogsPreviewer projectRef="123" queryType={queryType} tableName={tableName} />)
// // reset mock so that we can check for selection call
// get.mockClear()
// for (const text of tableTexts) {
// await screen.findByText(text)
// }
// const row = await screen.findByText(tableTexts[0])
// fireEvent.click(row)
// await waitFor(() => {
// expect(get).toHaveBeenCalledWith(
// expect.stringContaining('iso_timestamp_start'),
// expect.anything()
// )
// expect(get).not.toHaveBeenCalledWith(
// expect.stringContaining('iso_timestamp_end'),
// expect.anything()
// )
// })
// for (const text of selectionTexts) {
// await screen.findAllByText(text)
// }
// }
// )
test.skip('Search will trigger a log refresh', async () => {
render(<LogsPreviewer projectRef="123" queryType="auth" />)
@@ -220,249 +76,3 @@ test.skip('log event chart', async () => {
await waitFor(() => screen.queryByText(/some-uuid123/) === null)
})
test.skip('s= query param will populate the search bar', async () => {
// const router = routerMock
})
test.skip('te= query param will populate the timestamp to input', async () => {
// get time 20 mins before
// const newDate = new Date()
// newDate.setMinutes(new Date().getMinutes() - 20)
// const iso = newDate.toISOString()
// const router = defaultRouterMock()
// router.query = { ...router.query, ite: iso }
// useRouter.mockReturnValue(router)
// useParams.mockReturnValue(router.query)
// render(<LogsPreviewer projectRef="123" tableName={LogsTableName.EDGE} />)
// await waitFor(() => {
// expect(get).toHaveBeenCalledWith(
// expect.stringContaining(`iso_timestamp_end=${encodeURIComponent(iso)}`),
// expect.anything()
// )
// })
})
// test.skip('ts= query param will populate the timestamp from input', async () => {
// // get time 20 mins before
// const newDate = new Date()
// newDate.setMinutes(new Date().getMinutes() - 20)
// const iso = newDate.toISOString()
// const router = defaultRouterMock()
// router.query = { ...router.query, its: iso }
// useRouter.mockReturnValue(router)
// useParams.mockReturnValue(router.query)
// render(<LogsPreviewer projectRef="123" tableName={LogsTableName.EDGE} />)
// await waitFor(() => {
// expect(get).toHaveBeenCalledWith(
// expect.stringContaining(`iso_timestamp_start=${encodeURIComponent(iso)}`),
// expect.anything()
// )
// })
// })
// test.skip('load older btn will fetch older logs', async () => {
// get.mockImplementation((url) => {
// if (url.includes('count')) {
// return {}
// }
// return {
// result: [logDataFixture({ id: 'some-uuid123', status_code: 200, method: 'GET' })],
// }
// })
// render(<LogsPreviewer queryType="api" projectRef="123" tableName={LogsTableName.EDGE} />)
// // should display first log but not second
// await waitFor(() => screen.getByText('GET'))
// await expect(screen.findByText('POST')).rejects.toThrow()
// get.mockResolvedValueOnce({
// result: [logDataFixture({ id: 'some-uuid234', status_code: 203, method: 'POST' })],
// })
// // should display first and second log
// userEvent.click(await screen.findByText('Load older'))
// await screen.findByText('GET')
// await screen.findByText('POST')
// expect(get).toHaveBeenCalledWith(expect.stringContaining('timestamp_end='), expect.anything())
// })
// test.skip('bug: load older btn does not error out when previous page is empty', async () => {
// // bugfix for https://sentry.io/organizations/supabase/issues/2903331460/?project=5459134&referrer=slack
// get.mockImplementation((url) => {
// if (url.includes('count')) {
// return {}
// }
// return { result: [] }
// })
// render(<LogsPreviewer queryType="api" projectRef="123" tableName={LogsTableName.EDGE} />)
// userEvent.click(await screen.findByText('Load older'))
// // NOTE: potential race condition, since we are asserting that something DOES NOT EXIST
// // wait for 500s to make sure all ui logic is complete
// // need to wrap in act because internal react state is changing during this time.
// await act(async () => await wait(100))
// // clicking load older multiple times should not give error
// await waitFor(() => {
// expect(screen.queryByText(/Sorry/)).toBeNull()
// expect(screen.queryByText(/An error occurred/)).toBeNull()
// expect(screen.queryByText(/undefined/)).toBeNull()
// })
// })
// test.skip('log event chart hide', async () => {
// get.mockImplementation((url) => {
// return { result: [] }
// })
// render(<LogsPreviewer projectRef="123" tableName={LogsTableName.EDGE} />)
// await screen.findByText(/No data/)
// const toggle = await screen.findByText(/Chart/)
// userEvent.click(toggle)
// await expect(screen.findByText('Events')).rejects.toThrow()
// })
// test.skip('bug: nav backwards with params change results in ui changing', async () => {
// // bugfix for https://sentry.io/organizations/supabase/issues/2903331460/?project=5459134&referrer=slack
// get.mockImplementation((url) => {
// if (url.includes('count')) {
// return {}
// }
// return { data: [] }
// })
// const { container, rerender } = render(
// <LogsPreviewer projectRef="123" tableName={LogsTableName.EDGE} />
// )
// await expect(screen.findByDisplayValue('simple-query')).rejects.toThrow()
// const router = defaultRouterMock()
// router.query = { ...router.query, s: 'simple-query' }
// useRouter.mockReturnValue(router)
// useParams.mockReturnValue(router.query)
// rerender(<LogsPreviewer projectRef="123" tableName={LogsTableName.EDGE} />)
// await screen.findByDisplayValue('simple-query')
// })
// test.skip('bug: nav to explorer preserves newlines', async () => {
// get.mockImplementation((url) => {
// return { result: [] }
// })
// render(<LogsPreviewer queryType="api" projectRef="123" tableName={LogsTableName.EDGE} />)
// const button = screen.getByRole('link', { name: 'Explore via query' })
// expect(button.href).toContain(encodeURIComponent('\n'))
// })
// test.skip('filters alter generated query', async () => {
// render(<LogsPreviewer queryType="api" projectRef="123" tableName={LogsTableName.EDGE} />)
// userEvent.click(await screen.findByRole('button', { name: 'Status' }))
// userEvent.click(await screen.findByText(/500 error codes/))
// userEvent.click(await screen.findByText(/200 codes/))
// userEvent.click(await screen.findByText(/Apply/))
// await waitFor(() => {
// // counts are adjusted
// expect(get).toHaveBeenCalledWith(
// expect.stringMatching(/count.+\*.+as.count.+where.+500.+599/),
// expect.anything()
// )
// expect(get).toHaveBeenCalledWith(expect.stringContaining('500'), expect.anything())
// expect(get).toHaveBeenCalledWith(expect.stringContaining('599'), expect.anything())
// expect(get).toHaveBeenCalledWith(expect.stringContaining('200'), expect.anything())
// expect(get).toHaveBeenCalledWith(expect.stringContaining('299'), expect.anything())
// expect(get).toHaveBeenCalledWith(expect.stringContaining('where'), expect.anything())
// expect(get).toHaveBeenCalledWith(expect.stringContaining('and'), expect.anything())
// })
// // should be able to clear the filters
// userEvent.click(await screen.findByRole('button', { name: 'Status' }))
// userEvent.click(await screen.findByRole('button', { name: 'Clear' }))
// // get.mockClear()
// userEvent.click(await screen.findByRole('button', { name: 'Status' }))
// userEvent.click(await screen.findByText(/400 codes/))
// userEvent.click(await screen.findByText(/Apply/))
// await waitFor(() => {
// // counts are adjusted
// expect(get).not.toHaveBeenCalledWith(
// expect.stringMatching(/count.+\*.+as.count.+where.+500.+599/),
// expect.anything()
// )
// expect(get).toHaveBeenCalledWith(
// expect.stringMatching(/count.+\*.+as.count.+where.+400.+499/),
// expect.anything()
// )
// expect(get).not.toHaveBeenCalledWith(expect.stringContaining('500'), expect.anything())
// expect(get).not.toHaveBeenCalledWith(expect.stringContaining('599'), expect.anything())
// expect(get).not.toHaveBeenCalledWith(expect.stringContaining('200'), expect.anything())
// expect(get).not.toHaveBeenCalledWith(expect.stringContaining('299'), expect.anything())
// expect(get).toHaveBeenCalledWith(expect.stringContaining('400'), expect.anything())
// expect(get).toHaveBeenCalledWith(expect.stringContaining('499'), expect.anything())
// expect(get).toHaveBeenCalledWith(expect.stringContaining('where'), expect.anything())
// expect(get).toHaveBeenCalledWith(expect.stringContaining('and'), expect.anything())
// })
// })
// test.skip('filters accept filterOverride', async () => {
// render(
// <LogsPreviewer
// queryType="api"
// projectRef="123"
// tableName={LogsTableName.FUNCTIONS}
// filterOverride={{ 'my.nestedkey': 'myvalue' }}
// />
// )
// await waitFor(() => {
// expect(get).toHaveBeenCalledWith(expect.stringContaining('my.nestedkey'), expect.anything())
// expect(get).toHaveBeenCalledWith(expect.stringContaining('myvalue'), expect.anything())
// })
// })
// describe.each(['free', 'pro', 'team', 'enterprise'])('upgrade modal for %s', (key) => {
// beforeEach(() => {
// useOrgSubscriptionQuery.mockReturnValue({
// data: {
// plan: {
// id: key,
// },
// },
// })
// })
// test.skip('based on query params', async () => {
// const router = defaultRouterMock()
// router.query = {
// ...router.query,
// q: 'some_query',
// its: dayjs().subtract(4, 'months').toISOString(),
// ite: dayjs().toISOString(),
// }
// useRouter.mockReturnValue(router)
// useParams.mockReturnValue(router.query)
// render(<LogsPreviewer projectRef="123" tableName={LogsTableName.EDGE} />)
// // await screen.findByText('Log retention') // assert modal title is present
// })
// })
// test.skip('datepicker onChange will set the query params for outbound api request', async () => {
// useOrgSubscriptionQuery.mockReturnValue({
// data: {
// plan: {
// id: 'enterprise',
// },
// },
// })
// get.mockImplementation((url) => {
// return { result: [] }
// })
// render(<LogsPreviewer queryType="api" projectRef="123" tableName={LogsTableName.EDGE} />)
// // renders time locally
// userEvent.click(await screen.findByText('Custom'))
// // inputs with local time
// const toHH = await screen.findByDisplayValue('23')
// userEvent.clear(toHH)
// userEvent.type(toHH, '12')
// userEvent.click(await screen.findByText('20'), { selector: '.react-datepicker__day' })
// userEvent.click(await screen.findByText('21'), { selector: '.react-datepicker__day' })
// userEvent.click(await screen.findByText('Apply'))
// await waitFor(() => {
// expect(get).toHaveBeenCalledWith(
// expect.stringMatching(/.+select.+event_message.+iso_timestamp_end=/),
// expect.anything()
// )
// })
// })
+5
View File
@@ -13,6 +13,11 @@ export interface Organization {
restriction_data: Record<string, string> | null
managed_by: 'supabase' | 'vercel-marketplace' | 'aws-marketplace'
partner_id?: string
plan: {
id: 'free' | 'pro' | 'team' | 'enterprise'
name: string
}
usage_billing_enabled: boolean
}
/**
+13 -1
View File
@@ -5963,6 +5963,11 @@ export interface components {
is_owner: boolean
name: string
opt_in_tags: string[]
plan: {
/** @enum {string} */
id: 'free' | 'pro' | 'team' | 'enterprise'
name: string
}
restriction_data: {
[key: string]: string
} | null
@@ -5971,6 +5976,7 @@ export interface components {
slug: string
stripe_customer_id: string | null
subscription_id: string | null
usage_billing_enabled: boolean
}
OrganizationRoleResponse: {
org_scoped_roles: {
@@ -6033,10 +6039,16 @@ export interface components {
id: number
name: string
opt_in_tags: string[]
plan: {
/** @enum {string} */
id?: 'free' | 'pro' | 'team' | 'enterprise'
name?: string
}
projects: components['schemas']['OrganizationSlugProject'][]
restriction_data: Record<string, never> | null
restriction_status: Record<string, never> | null
slug: string
usage_billing_enabled: boolean
}
OrgDocumentUrlResponse: {
fileUrl: string
@@ -13785,7 +13797,7 @@ export interface operations {
UsageApiController_getApiCounts: {
parameters: {
query: {
interval: 'minutely' | 'hourly' | 'daily'
interval: 'minutely' | 'hourly' | 'daily' | '15min' | '30min' | '1hr' | '3hr' | '1day'
}
header?: never
path: {