mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Ensure read only privileges in project settings pages
This commit is contained in:
1 parent
c6bcdc69c4
commit
bd09b45a73
12 files changed
+283
-72
No files matched your search
@@ -35,7 +35,7 @@ const BackupsList: FC<Props> = ({}) => {
|
||||
icon={<IconClock size="large" />}
|
||||
primaryText="Free Plan does not include project backups."
|
||||
projectRef={projectRef}
|
||||
secondaryText="Please upgrade to the Pro plan for up to 7 days of scheduled backups."
|
||||
secondaryText="Upgrade to the Pro plan for up to 7 days of scheduled backups."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FC, useState, useEffect, Fragment } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useStore } from 'hooks'
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import { pluckObjectFields } from 'lib/helpers'
|
||||
import Loading from 'components/ui/Loading'
|
||||
import Panel from 'components/ui/Panel'
|
||||
@@ -12,6 +12,7 @@ import ToggleField from 'components/to-be-cleaned/forms/ToggleField'
|
||||
import SchemaFormPanel from 'components/to-be-cleaned/forms/SchemaFormPanel'
|
||||
import { Input } from 'ui'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
interface Props {}
|
||||
|
||||
@@ -127,6 +128,11 @@ export const PgbouncerConfig: FC<ConfigProps> = observer(
|
||||
({ projectRef, bouncerInfo, connectionInfo }) => {
|
||||
const { ui } = useStore()
|
||||
|
||||
const canUpdateConnectionPoolingConfiguration = checkPermissions(
|
||||
PermissionAction.UPDATE,
|
||||
'projects'
|
||||
)
|
||||
|
||||
const [updates, setUpdates] = useState<any>({
|
||||
pgbouncer_enabled: bouncerInfo.pgbouncer_enabled,
|
||||
pool_mode: bouncerInfo.pool_mode || 'transaction',
|
||||
@@ -198,6 +204,8 @@ export const PgbouncerConfig: FC<ConfigProps> = observer(
|
||||
onChangeModel={(model: any) => setUpdates(model)}
|
||||
onSubmit={(model: any) => updateConfig(model)}
|
||||
onReset={() => setUpdates(bouncerInfo)}
|
||||
disabled={!canUpdateConnectionPoolingConfiguration}
|
||||
disabledMessage="You need additional permissions to update connection pooling settings"
|
||||
>
|
||||
<div className="space-y-6 py-4">
|
||||
<ToggleField name="pgbouncer_enabled" />
|
||||
|
||||
+111
-21
@@ -1,8 +1,10 @@
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { FC, useState } from 'react'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { Button, IconAlertCircle, IconExternalLink, IconGlobe, IconLock } from 'ui'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { useParams } from 'hooks'
|
||||
import { checkPermissions, useParams } from 'hooks'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { FormPanel, FormHeader } from 'components/ui/Forms'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
@@ -12,6 +14,62 @@ import DisallowAllModal from './DisallowAllModal'
|
||||
import AllowAllModal from './AllowAllModal'
|
||||
import { useNetworkRestrictionsQuery } from 'data/network-restrictions/network-restrictions-query'
|
||||
|
||||
const AllowAllAccessButton: FC<{ disabled: boolean; onClick: (value: boolean) => void }> = ({
|
||||
disabled,
|
||||
onClick,
|
||||
}) => (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Button type="default" disabled={disabled} onClick={() => onClick(true)}>
|
||||
Allow all access
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{disabled && (
|
||||
<Tooltip.Content align="center" side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200 w-[250px]',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update network restrictions
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
)
|
||||
|
||||
const DisallowAllAccessButton: FC<{ disabled: boolean; onClick: (value: boolean) => void }> = ({
|
||||
disabled,
|
||||
onClick,
|
||||
}) => (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Button type="default" disabled={disabled} onClick={() => onClick(true)}>
|
||||
Restrict all access
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{disabled && (
|
||||
<Tooltip.Content align="center" side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200 w-[250px]',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update network restrictions
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
)
|
||||
|
||||
const NetworkRestrictions = ({}) => {
|
||||
const { ref } = useParams()
|
||||
|
||||
@@ -21,6 +79,8 @@ const NetworkRestrictions = ({}) => {
|
||||
const [selectedRestrictionToRemove, setSelectedRestrictionToRemove] = useState<string>()
|
||||
const { data, isLoading } = useNetworkRestrictionsQuery({ projectRef: ref })
|
||||
|
||||
const canUpdateNetworkRestrictions = checkPermissions(PermissionAction.UPDATE, 'projects')
|
||||
|
||||
const hasAccessToRestrictions = data?.entitlement === 'allowed'
|
||||
const restrictedIps = data?.config.dbAllowedCidrs ?? []
|
||||
const restrictionStatus = data?.status ?? ''
|
||||
@@ -48,7 +108,31 @@ const NetworkRestrictions = ({}) => {
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
<Button onClick={() => setIsAddingAddress(true)}>Add restriction</Button>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
disabled={!canUpdateNetworkRestrictions}
|
||||
onClick={() => setIsAddingAddress(true)}
|
||||
>
|
||||
Add restriction
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateNetworkRestrictions && (
|
||||
<Tooltip.Content align="center" side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200 w-[250px]',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update network restrictions
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
@@ -75,12 +159,14 @@ const NetworkRestrictions = ({}) => {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button type="default" onClick={() => setIsAllowingAll(true)}>
|
||||
Allow all access
|
||||
</Button>
|
||||
<Button type="default" onClick={() => setIsDisallowingAll(true)}>
|
||||
Restrict all access
|
||||
</Button>
|
||||
<AllowAllAccessButton
|
||||
disabled={!canUpdateNetworkRestrictions}
|
||||
onClick={setIsAllowingAll}
|
||||
/>
|
||||
<DisallowAllAccessButton
|
||||
disabled={!canUpdateNetworkRestrictions}
|
||||
onClick={setIsDisallowingAll}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Panel.Content>
|
||||
@@ -101,9 +187,10 @@ const NetworkRestrictions = ({}) => {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Button type="default" onClick={() => setIsDisallowingAll(true)}>
|
||||
Restrict all access
|
||||
</Button>
|
||||
<DisallowAllAccessButton
|
||||
disabled={!canUpdateNetworkRestrictions}
|
||||
onClick={setIsDisallowingAll}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : isDisallowedAll ? (
|
||||
@@ -125,9 +212,10 @@ const NetworkRestrictions = ({}) => {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Button type="default" onClick={() => setIsAllowingAll(true)}>
|
||||
Allow all access
|
||||
</Button>
|
||||
<AllowAllAccessButton
|
||||
disabled={!canUpdateNetworkRestrictions}
|
||||
onClick={setIsAllowingAll}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -146,12 +234,14 @@ const NetworkRestrictions = ({}) => {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button type="default" onClick={() => setIsAllowingAll(true)}>
|
||||
Allow all access
|
||||
</Button>
|
||||
<Button type="default" onClick={() => setIsDisallowingAll(true)}>
|
||||
Restrict all access
|
||||
</Button>
|
||||
<AllowAllAccessButton
|
||||
disabled={!canUpdateNetworkRestrictions}
|
||||
onClick={setIsAllowingAll}
|
||||
/>
|
||||
<DisallowAllAccessButton
|
||||
disabled={!canUpdateNetworkRestrictions}
|
||||
onClick={setIsDisallowingAll}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{restrictedIps.map((ip) => {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import Link from 'next/link'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useParams, useStore } from 'hooks'
|
||||
import { checkPermissions, useParams, useStore } from 'hooks'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { Button, IconDownload, Toggle, IconExternalLink, IconLoader, Alert } from 'ui'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import {
|
||||
FormHeader,
|
||||
FormPanel,
|
||||
@@ -25,6 +27,8 @@ const SSLConfiguration = () => {
|
||||
})
|
||||
const { mutateAsync: updateSSLEnforcement } = useSSLEnforcementUpdateMutation()
|
||||
|
||||
const canUpdateSSLEnforcement = checkPermissions(PermissionAction.UPDATE, 'projects')
|
||||
|
||||
const hasAccessToSSLEnforcement = !sslEnforcementConfiguration?.isNotAllowed
|
||||
const env = process.env.NEXT_PUBLIC_ENVIRONMENT === 'prod' ? 'prod' : 'staging'
|
||||
const hasSSLCertificate =
|
||||
@@ -115,11 +119,30 @@ const SSLConfiguration = () => {
|
||||
{(isLoading || isSubmitting) && (
|
||||
<IconLoader className="animate-spin" strokeWidth={1.5} size={16} />
|
||||
)}
|
||||
<Toggle
|
||||
checked={isEnforced}
|
||||
disabled={isLoading || isSubmitting}
|
||||
onChange={toggleSSLEnforcement}
|
||||
/>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Toggle
|
||||
checked={isEnforced}
|
||||
disabled={!canUpdateSSLEnforcement || isLoading || isSubmitting}
|
||||
onChange={toggleSSLEnforcement}
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateSSLEnforcement && (
|
||||
<Tooltip.Content align="center" side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200 w-[250px]',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
You need additional permissions to update SSL configurations
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</FormSectionContent>
|
||||
</FormSection>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { FC, useEffect } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Badge, Button, IconAlertCircle, IconInfo, Loading } from 'ui'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import { formatBytes } from 'lib/helpers'
|
||||
import { PRICING_TIER_PRODUCT_IDS, USAGE_APPROACHING_THRESHOLD } from 'lib/constants'
|
||||
import SparkBar from 'components/ui/SparkBar'
|
||||
@@ -9,8 +12,6 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
import { USAGE_BASED_PRODUCTS } from 'components/interfaces/Billing/Billing.constants'
|
||||
import { ProjectUsageResponse, useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
import { useRouter } from 'next/router'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
|
||||
interface Props {
|
||||
projectRef?: string
|
||||
@@ -21,6 +22,11 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
const { data: usage, error, isLoading } = useProjectUsageQuery({ projectRef })
|
||||
const router = useRouter()
|
||||
|
||||
const canUpdateSubscription = checkPermissions(
|
||||
PermissionAction.BILLING_WRITE,
|
||||
'stripe.subscriptions'
|
||||
)
|
||||
|
||||
const subscriptionTier = ui.selectedProject?.subscription_tier
|
||||
|
||||
const projectHasNoLimits =
|
||||
@@ -128,14 +134,16 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
usageElement = (
|
||||
<div className="flex items-center justify-between">
|
||||
<span>Not included in {planName} tier</span>
|
||||
<Button
|
||||
size="tiny"
|
||||
onClick={() =>
|
||||
router.push(`/project/${projectRef}/settings/billing/update`)
|
||||
}
|
||||
>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
{canUpdateSubscription && (
|
||||
<Button
|
||||
size="tiny"
|
||||
onClick={() =>
|
||||
router.push(`/project/${projectRef}/settings/billing/update`)
|
||||
}
|
||||
>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
} else if (showUsageExceedMessage) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { useState } from 'react'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { Button, Form, IconClock, Input, Listbox } from 'ui'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query'
|
||||
import { useProjectStorageConfigUpdateUpdateMutation } from 'data/config/project-storage-config-update-mutation'
|
||||
import UpgradeToPro from 'components/ui/UpgradeToPro'
|
||||
import { convertFromBytes, convertToBytes } from './StorageSettings.utils'
|
||||
import { StorageSizeUnits, STORAGE_FILE_SIZE_LIMIT_MAX_BYTES } from './StorageSettings.constants'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
export type StorageSettingsProps = {
|
||||
projectRef: string | undefined
|
||||
@@ -42,6 +44,8 @@ const StorageConfig = ({ config, projectRef }: any) => {
|
||||
const [selectedUnit, setSelectedUnit] = useState(unit)
|
||||
let initialValues = { fileSizeLimit: value, unformattedFileSizeLimit: fileSizeLimit }
|
||||
|
||||
const canUpdateStorageSettings = checkPermissions(PermissionAction.UPDATE, 'projects')
|
||||
|
||||
const formattedMaxSizeBytes = `${new Intl.NumberFormat('en-US').format(
|
||||
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES
|
||||
)} bytes`
|
||||
@@ -184,13 +188,20 @@ const StorageConfig = ({ config, projectRef }: any) => {
|
||||
icon={<IconClock size="large" />}
|
||||
primaryText="Free Plan has a fixed upload file size limit of 50 MB."
|
||||
projectRef={projectRef}
|
||||
secondaryText="Please upgrade to Pro plan for a configurable upload file size limit of up to 5 GB."
|
||||
secondaryText="Upgrade to the Pro plan for a configurable upload file size limit of up to 5 GB."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="border-t border-scale-400" />
|
||||
<div className="flex justify-between px-8 py-4">
|
||||
<div className="flex items-center justify-end w-full gap-2">
|
||||
<div className="flex items-center justify-between w-full gap-2">
|
||||
{!canUpdateStorageSettings ? (
|
||||
<p className="text-sm text-scale-1000">
|
||||
You need additional permissions to update storage settings
|
||||
</p>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="default"
|
||||
@@ -204,7 +215,9 @@ const StorageConfig = ({ config, projectRef }: any) => {
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={isSubmitting}
|
||||
disabled={!hasChanges && hasChanges !== undefined}
|
||||
disabled={
|
||||
!canUpdateStorageSettings || (!hasChanges && hasChanges !== undefined)
|
||||
}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
|
||||
@@ -2,10 +2,19 @@ import { AutoForm } from 'uniforms-bootstrap4'
|
||||
import Ajv from 'ajv'
|
||||
import { JSONSchemaBridge } from 'uniforms-bridge-json-schema'
|
||||
|
||||
const SchemaForm = ({ schema, model, children, formRef, onSubmit, onChangeModel = () => {} }) => {
|
||||
const SchemaForm = ({
|
||||
schema,
|
||||
model,
|
||||
disabled = false,
|
||||
children,
|
||||
formRef,
|
||||
onSubmit,
|
||||
onChangeModel = () => {},
|
||||
}) => {
|
||||
const validatedSchema = new JSONSchemaBridge(schema, createValidator(schema))
|
||||
return (
|
||||
<AutoForm
|
||||
disabled={disabled}
|
||||
onChangeModel={onChangeModel}
|
||||
ref={formRef}
|
||||
schema={validatedSchema}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import SchemaForm from './SchemaForm'
|
||||
import React, { useState } from 'react'
|
||||
import { Button } from 'ui'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import SchemaForm from './SchemaForm'
|
||||
|
||||
export default function SchemaFormPanel({
|
||||
schema,
|
||||
@@ -13,6 +14,8 @@ export default function SchemaFormPanel({
|
||||
submitLabel = 'Save',
|
||||
cancelLabel = 'Cancel',
|
||||
message = '',
|
||||
disabled = false,
|
||||
disabledMessage = '',
|
||||
}) {
|
||||
let formRef
|
||||
const [submitButtonLoading, setSubmitButtonLoading] = useState(false)
|
||||
@@ -48,29 +51,34 @@ export default function SchemaFormPanel({
|
||||
<div className="flex-1 text-left">
|
||||
<h6>{title || ''}</h6>
|
||||
</div>
|
||||
<div
|
||||
className={`flex transition duration-150 ${
|
||||
hasChanged ? 'opacity-100' : 'cursor-default opacity-0'
|
||||
}`}
|
||||
>
|
||||
<Button onClick={onClickCancel} type="default" disabled={!hasChanged}>
|
||||
{cancelLabel || 'Cancel'}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onClickSubmit}
|
||||
loading={submitButtonLoading}
|
||||
disabled={!hasChanged}
|
||||
type="primary"
|
||||
className="ml-2 hover:border-green-500"
|
||||
{disabled && disabledMessage ? (
|
||||
<p className="text-sm text-scale-1000">{disabledMessage}</p>
|
||||
) : (
|
||||
<div
|
||||
className={`flex transition duration-150 ${
|
||||
hasChanged ? 'opacity-100' : 'cursor-default opacity-0'
|
||||
}`}
|
||||
>
|
||||
{submitLabel || 'Save'}
|
||||
</Button>
|
||||
</div>
|
||||
<Button onClick={onClickCancel} type="default" disabled={!hasChanged}>
|
||||
{cancelLabel || 'Cancel'}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onClickSubmit}
|
||||
loading={submitButtonLoading}
|
||||
disabled={disabled || !hasChanged}
|
||||
type="primary"
|
||||
className="ml-2 hover:border-green-500"
|
||||
>
|
||||
{submitLabel || 'Save'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<span className="text-sm text-scale-900">{message}</span>
|
||||
</div>
|
||||
|
||||
<div className="Form section-block--body px-6 py-3">
|
||||
<SchemaForm
|
||||
disabled={disabled}
|
||||
onChangeModel={(model) => {
|
||||
setHasChanged(true)
|
||||
if (onChangeModel) onChangeModel(model)
|
||||
|
||||
@@ -2,8 +2,11 @@ import { Button } from 'ui'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import Link from 'next/link'
|
||||
import { FC, ReactNode } from 'react'
|
||||
import { useStore } from 'hooks'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
|
||||
import { checkPermissions, useStore, useFlag, useParams } from 'hooks'
|
||||
import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
interface Props {
|
||||
icon?: ReactNode
|
||||
@@ -14,8 +17,16 @@ interface Props {
|
||||
|
||||
const UpgradeToPro: FC<Props> = ({ icon, primaryText, projectRef, secondaryText }) => {
|
||||
const { ui } = useStore()
|
||||
const { ref } = useParams()
|
||||
const tier = ui.selectedProject?.subscription_tier
|
||||
|
||||
const canUpdateSubscription = checkPermissions(
|
||||
PermissionAction.BILLING_WRITE,
|
||||
'stripe.subscriptions'
|
||||
)
|
||||
const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
const isEnterprise = tier === PRICING_TIER_PRODUCT_IDS.ENTERPRISE
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
@@ -33,13 +44,52 @@ const UpgradeToPro: FC<Props> = ({ icon, primaryText, projectRef, secondaryText
|
||||
<p className="text-sm text-scale-1100">{secondaryText}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Link href={`/project/${projectRef}/settings/billing/update`}>
|
||||
<a>
|
||||
<Button type="primary">
|
||||
{tier === PRICING_TIER_PRODUCT_IDS.FREE ? 'Upgrade to Pro' : 'Modify subscription'}
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Button type="primary" disabled={!canUpdateSubscription || projectUpdateDisabled}>
|
||||
<Link
|
||||
href={
|
||||
isEnterprise
|
||||
? `/project/${ref}/settings/billing/update/enterprise`
|
||||
: `/project/${ref}/settings/billing/update`
|
||||
}
|
||||
>
|
||||
<a>
|
||||
{tier === PRICING_TIER_PRODUCT_IDS.FREE
|
||||
? 'Upgrade to Pro'
|
||||
: 'Modify subscription'}
|
||||
</a>
|
||||
</Link>
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateSubscription || projectUpdateDisabled ? (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'border border-scale-200 text-center', //border
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
{projectUpdateDisabled ? (
|
||||
<>
|
||||
Subscription changes are currently disabled.
|
||||
<br />
|
||||
Our engineers are working on a fix.
|
||||
</>
|
||||
) : !canUpdateSubscription ? (
|
||||
'You need additional permissions to amend subscriptions'
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -65,7 +65,7 @@ const PITR = () => {
|
||||
primaryText="Point in time recovery is a Pro plan add-on."
|
||||
secondaryText={
|
||||
tier === PRICING_TIER_PRODUCT_IDS.FREE
|
||||
? 'Please upgrade to the Pro plan with the PITR add-on selected to enable point in time recovery for your project.'
|
||||
? 'Upgrade to the Pro plan with the PITR add-on selected to enable point in time recovery for your project.'
|
||||
: 'Please enable the add-on to enable point in time recovery for your project.'
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -36,7 +36,6 @@ interface SettingsProps {
|
||||
|
||||
const Settings: FC<SettingsProps> = ({ project }) => {
|
||||
const { ui } = useStore()
|
||||
const projectTier = ui.selectedProject?.subscription_tier
|
||||
|
||||
const {
|
||||
data: subscription,
|
||||
|
||||
@@ -39,6 +39,9 @@ Sentry.init({
|
||||
// Safe to ignore since it's not a user-facing issue + we've not received any user feedback/report about it
|
||||
// Ref: https://github.com/supabase/supabase/pull/9729
|
||||
'The provided `href` (/org/[slug]/general) value is missing query values (slug)',
|
||||
'The provided `href` (/org/[slug]/team) value is missing query values (slug)',
|
||||
'The provided `href` (/org/[slug]/billing) value is missing query values (slug)',
|
||||
'The provided `href` (/org/[slug]/invoices) value is missing query values (slug)',
|
||||
],
|
||||
beforeSend: (event) => filterConsoleErrors(event),
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user