Chore/clean up feature flags 300626 (#47429)

## Context

Cleans up a number of stale feature flags that have been enabled for all
users for more than 3 months
- enableSmartRegion
- SentryLogDrain
- axiomLogDrain
- S3logdrain
- Last9LogDrain
- otlpLogDrain 
- ShowPrettyExplain
- pgdeltaDiff
- CustomOauthProviders
- timezonePicker

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

* **New Features**
  * Timezone selector is now always available in the user menu.
  * Log drain “Type” options are expanded in the creation flow.
* Custom auth providers now appear when the custom providers setting is
enabled.
* **Bug Fixes**
* Smart region selection and related queries now follow the selected
cloud provider.
* PG Delta Diff preview availability and SQL editor “EXPLAIN” routing
now follow the latest enabled settings (including platform-only preview
behavior).
* **Changes**
* Removed the Storage List-V2 migration callout from the Storage
settings page.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Joshen Lim authored and GitHub committed 2026-07-01 14:42:08 +08:00
1 parent 963ab3e63b
commit 7203a97a90
12 files changed
+222 -431

No files matched your search

@@ -13,6 +13,7 @@ import {
} from 'react'
import { useFeaturePreviews } from './useFeaturePreviews'
import { IS_PLATFORM } from '@/lib/constants'
import { EMPTY_OBJ } from '@/lib/void'
type FeaturePreviewContextType = {
@@ -43,6 +44,11 @@ export const FeaturePreviewContextProvider = ({ children }: PropsWithChildren) =
const initializeFlags = useEffectEvent(() => {
setFlags(
featurePreviews.reduce((a, b) => {
// Platform-only previews can never be enabled outside the hosted platform
if (!IS_PLATFORM && b.isPlatformOnly) {
return { ...a, [b.key]: false }
}
const defaultOptIn = b.isDefaultOptIn
const localStorageValue = safeLocalStorage.getItem(b.key)
return {
@@ -101,8 +107,7 @@ export const useUnifiedLogsPreview = () => {
export const useIsPgDeltaDiffEnabled = () => {
const { flags } = useFeaturePreviewContext()
const pgDeltaDiffEnabled = useFlag('pgdeltaDiff')
return pgDeltaDiffEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_PG_DELTA_DIFF]
return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_PG_DELTA_DIFF]
}
export const useIsAdvisorRulesEnabled = () => {
@@ -20,7 +20,6 @@ export type FeaturePreview = {
}
export const useFeaturePreviews = (): FeaturePreview[] => {
const pgDeltaDiffEnabled = useFlag('pgdeltaDiff')
const platformWebhooksEnabled = useFlag('platformWebhooks')
const jitDbAccessEnabled = useFlag('jitDbAccess')
const isMarketplaceEnabled = useFlag('marketplaceIntegrations')
@@ -68,7 +67,7 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
isNew: false,
isPlatformOnly: true,
isDefaultOptIn: true,
enabled: pgDeltaDiffEnabled,
enabled: true,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_PLATFORM_WEBHOOKS,
@@ -111,12 +110,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
getRoute: (ref?: string) => `/project/${ref}/integrations`,
},
].sort((a, b) => Number(b.isNew) - Number(a.isNew)),
[
unifiedLogsDefaultOptIn,
pgDeltaDiffEnabled,
platformWebhooksEnabled,
jitDbAccessEnabled,
isMarketplaceEnabled,
]
[unifiedLogsDefaultOptIn, platformWebhooksEnabled, jitDbAccessEnabled, isMarketplaceEnabled]
)
}
@@ -1,5 +1,5 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { IS_PLATFORM, useFlag } from 'common'
import { IS_PLATFORM } from 'common'
import Link from 'next/link'
import { ReactNode, useEffect, useMemo, useRef } from 'react'
import { useForm } from 'react-hook-form'
@@ -51,6 +51,7 @@ import {
logDrainHeaderEntriesSchema,
type LogDrainHeaderRow,
} from './LogDrains.utils'
import { useEnabledLogDrainTypes } from './useEnabledLogDrainTypes'
import { TaxDisclaimer } from '@/components/interfaces/Billing/TaxDisclaimer'
import { Shortcut } from '@/components/ui/Shortcut'
import { LogDrainData } from '@/data/log-drains/log-drains-query'
@@ -347,12 +348,7 @@ export function LogDrainDestinationSheetForm({
)
}, [defaultValues, mode])
const sentryEnabled = useFlag('SentryLogDrain')
const s3Enabled = useFlag('S3logdrain')
const axiomEnabled = useFlag('axiomLogDrain')
const otlpEnabled = useFlag('otlpLogDrain')
const last9Enabled = useFlag('Last9LogDrain')
const syslogEnabled = useFlag('syslogLogDrain')
const enabledLogDrainTypes = useEnabledLogDrainTypes()
const formRef = useRef<HTMLFormElement>(null)
@@ -474,15 +470,7 @@ export function LogDrainDestinationSheetForm({
{LOG_DRAIN_TYPES.find((t) => t.value === type)?.name}
</SelectTrigger>
<SelectContent>
{LOG_DRAIN_TYPES.filter((t) => {
if (t.value === 'sentry') return sentryEnabled
if (t.value === 's3') return s3Enabled
if (t.value === 'axiom') return axiomEnabled
if (t.value === 'otlp') return otlpEnabled
if (t.value === 'last9') return last9Enabled
if (t.value === 'syslog') return syslogEnabled
return true
}).map((type) => (
{enabledLogDrainTypes.map((type) => (
<SelectItem
value={type.value}
key={type.value}
@@ -3,19 +3,9 @@ import { useFlag } from 'common'
import { LOG_DRAIN_TYPES } from './LogDrains.constants'
export function useEnabledLogDrainTypes() {
const sentryEnabled = useFlag('SentryLogDrain')
const s3Enabled = useFlag('S3logdrain')
const axiomEnabled = useFlag('axiomLogDrain')
const otlpEnabled = useFlag('otlpLogDrain')
const last9Enabled = useFlag('Last9LogDrain')
const syslogEnabled = useFlag('syslogLogDrain')
return LOG_DRAIN_TYPES.filter((t) => {
if (t.value === 'sentry') return sentryEnabled
if (t.value === 's3') return s3Enabled
if (t.value === 'axiom') return axiomEnabled
if (t.value === 'otlp') return otlpEnabled
if (t.value === 'last9') return last9Enabled
if (t.value === 'syslog') return syslogEnabled
return true
})
@@ -1,4 +1,4 @@
import { useFeatureFlags, useFlag, useParams } from 'common'
import { useFeatureFlags, useParams } from 'common'
import { UseFormReturn } from 'react-hook-form'
import type { CloudProvider } from 'shared-data'
import {
@@ -29,7 +29,7 @@ import { useDefaultRegionQuery } from '@/data/misc/get-default-region-query'
import { useOrganizationAvailableRegionsQuery } from '@/data/organizations/organization-available-regions-query'
import { useIncidentStatusQuery } from '@/data/platform/incident-status-query'
import type { DesiredInstanceSize } from '@/data/projects/new-project.constants'
import { BASE_PATH, PROVIDERS } from '@/lib/constants'
import { BASE_PATH } from '@/lib/constants'
interface RegionSelectorProps {
form: UseFormReturn<CreateProjectForm>
@@ -70,7 +70,7 @@ export const RegionSelector = ({
const cloudProvider = form.getValues('cloudProvider') as CloudProvider
const { hasLoaded: flagsLoaded } = useFeatureFlags()
const smartRegionEnabled = useFlag('enableSmartRegion')
const smartRegionEnabled = cloudProvider !== 'AWS_NIMBUS'
const { data: statusData } = useIncidentStatusQuery()
const { incidents = [] } = statusData ?? {}
@@ -100,7 +100,7 @@ export const RegionSelector = ({
availableRegionsData?.recommendations.specific.map((region) => region.code)
)
const availableRegions = getAvailableRegions(PROVIDERS[cloudProvider].id)
const availableRegions = getAvailableRegions(cloudProvider)
const regionsArray = Object.entries(availableRegions).map(([_key, value]) => {
return {
code: value.code,
@@ -126,7 +126,6 @@ export const SQLEditor = () => {
const getImpersonatedRoleState = useGetImpersonatedRoleState()
const databaseSelectorState = useDatabaseSelectorStateSnapshot()
const { aiOptInLevel } = useOrgAiOptInLevel()
const showPrettyExplain = useFlag('ShowPrettyExplain')
// [Ali] Kill switch to hide the SQL Editor Explain tab and its entry points
const disablePrettyExplain = useFlag('DisablePrettyExplainOnSqlEditor')
@@ -236,7 +235,7 @@ export const SQLEditor = () => {
if (id) {
sessionSnap.addResult(id, data.result, vars.autoLimit)
if (!disablePrettyExplain && showPrettyExplain && isExplainQuery(data.result)) {
if (!disablePrettyExplain && isExplainQuery(data.result)) {
sessionSnap.addExplainResult(id, data.result)
setActiveUtilityTab('explain')
} else if (activeUtilityTab === 'explain') {
@@ -1,165 +0,0 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import dayjs from 'dayjs'
import { useState } from 'react'
import { toast } from 'sonner'
import {
Button,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogSection,
DialogSectionSeparator,
DialogTitle,
DialogTrigger,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { TimestampInfo } from 'ui-patterns/TimestampInfo'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import { InlineLink } from '@/components/ui/InlineLink'
import { useProjectStorageConfigUpdateUpdateMutation } from '@/data/config/project-storage-config-update-mutation'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
// [Joshen] Will be decided by Storage team, temp setting to 15th December 2025 UTC (3 months buffer)
const MIGRATION_DEADLINE = '2025-12-15T00:00:00'
export const StorageListV2MigrationCallout = () => {
const deadline = dayjs(MIGRATION_DEADLINE).utc(true)
const currentDate = dayjs.utc()
const remainingMonths = Math.ceil(deadline.diff(currentDate, 'months', true))
return (
<Admonition
type="note"
title="A new version of Storage is available for your project"
description={
<>
<p>
Get access to the List-V2 endpoint for improved performance and the ability to enable
Analytics buckets to your storage system.
</p>
{remainingMonths <= 1 && (
<p>
Your project's Storage will be automatically upgraded by{' '}
<TimestampInfo
displayAs="utc"
utcTimestamp={MIGRATION_DEADLINE}
className="text-sm text-inherit font-medium"
labelFormat="DD MMM YYYY HH:mm (UTC)"
/>{' '}
if the upgrade is not completed by then.
</p>
)}
</>
}
actions={<StorageListV2MigrationDialog />}
/>
)
}
export const StorageListV2MigratingCallout = () => {
return (
<Admonition
type="note"
title="Project storage is currently upgrading"
description="This notice will be closed once the upgrade has been completed. Hang tight!"
/>
)
}
const StorageListV2MigrationDialog = () => {
const { ref } = useParams()
const { can: canUpdateStorageSettings } = useAsyncCheckPermissions(
PermissionAction.STORAGE_ADMIN_WRITE,
'*'
)
const [open, setOpen] = useState(false)
const { mutate: updateStorageConfig, isPending: isUpdating } =
useProjectStorageConfigUpdateUpdateMutation({
onSuccess: () => {
toast.success(`Project's storage will be upgraded shortly!`)
setOpen(false)
},
})
const onConfirmUpgrade = () => {
if (!ref) return console.error('Project ref is required')
updateStorageConfig({ projectRef: ref, external: { upstreamTarget: 'canary' } })
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<ButtonTooltip
variant="primary"
disabled={!canUpdateStorageSettings}
tooltip={{
content: {
side: 'bottom',
text: !canUpdateStorageSettings
? 'You need additional permissions to upgrade storage'
: undefined,
},
}}
>
Upgrade Storage
</ButtonTooltip>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Upgrade your project's Storage</DialogTitle>
<DialogDescription>
Get access to Analytics buckets and an improved list method
</DialogDescription>
</DialogHeader>
<DialogSectionSeparator />
<Admonition
type="warning"
className="rounded-none border-x-0 border-t-0"
title="Migration required to optimise the database schema for upgrade"
description="We recommend running the update during periods of lower activity, although minimal to no disruption is expected."
/>
<DialogSection className="flex flex-col gap-y-2">
<p className="text-sm">
Depending on the number of objects in your Storage, the migration can take up to 24
hours to finish.
</p>
<p className="text-sm">
The upgrade will increase your disk size to about 15 - 25% and IOPS will be used to
create new efficient indexes as well as denormalising tables.
</p>
<p className="text-sm">
Ensure that your database instance has not{' '}
<InlineLink href={`/project/${ref}/settings/compute-and-disk#disk-size`}>
scaled disk
</InlineLink>{' '}
within the last 6h and you have at least 60%{' '}
<InlineLink href={`/project/${ref}/settings/infrastructure#infrastructure-activity`}>
CPU capacity
</InlineLink>{' '}
before proceeding.
</p>
</DialogSection>
<DialogFooter>
<Button variant="default" disabled={isUpdating} onClick={() => setOpen(false)}>
Cancel
</Button>
<Button variant="primary" loading={isUpdating} onClick={() => onConfirmUpgrade()}>
Upgrade now
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
@@ -1,6 +1,6 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { IS_PLATFORM, useFlag, useParams } from 'common'
import { IS_PLATFORM, useParams } from 'common'
import { useEffect, useMemo, useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
@@ -29,10 +29,6 @@ import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import * as z from 'zod'
import { StorageFileSizeLimitErrorMessage } from './StorageFileSizeLimitErrorMessage'
import {
StorageListV2MigratingCallout,
StorageListV2MigrationCallout,
} from './StorageListV2MigrationCallout'
import {
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_CAPPED,
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_UNCAPPED,
@@ -70,8 +66,6 @@ export const StorageSettings = () => {
const { ref: projectRef } = useParams()
const { data: project } = useSelectedProjectQuery()
const showMigrationCallout = useFlag('storageMigrationCallout')
const { can: canReadStorageSettings, isLoading: isLoadingPermissions } = useAsyncCheckPermissions(
PermissionAction.STORAGE_ADMIN_READ,
'*'
@@ -88,10 +82,6 @@ export const StorageSettings = () => {
isSuccess,
isError,
} = useProjectStorageConfigQuery({ projectRef })
const isListV2UpgradeAvailable =
!!config && !config.capabilities.list_v2 && config.external.upstreamTarget === 'main'
const isListV2Upgrading =
!!config && !config.capabilities.list_v2 && config.external.upstreamTarget === 'canary'
const {
runCondition: sizeLimitCheckCondition,
@@ -282,209 +272,200 @@ export const StorageSettings = () => {
/>
)}
{isSuccess && (
<>
{showMigrationCallout && (
<>
{isListV2UpgradeAvailable && <StorageListV2MigrationCallout />}
{isListV2Upgrading && <StorageListV2MigratingCallout />}
</>
)}
<form id={formId} onSubmit={form.handleSubmit(onSubmit)}>
<Card>
<CardContent>
<FormField
control={form.control}
name="imageTransformationEnabled"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Enable image transformation"
description={
<>
Optimize and resize images on the fly.{' '}
<InlineLink
href={`${DOCS_URL}/guides/storage/serving/image-transformations`}
>
Learn more
</InlineLink>
.
</>
}
>
<FormControl>
<Switch
size="large"
disabled={
!hasAccessToImageTransformations || !canUpdateStorageSettings
}
checked={hasAccessToImageTransformations && field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItemLayout>
)}
/>
</CardContent>
<CardContent>
<FormField
control={form.control}
name="fileSizeLimit"
render={({ field }) => (
<FormItemLayout
hideMessage
layout="flex-row-reverse"
label="Global file size limit"
description={
<>
Restrict the size of files uploaded across all buckets.{' '}
<InlineLink
href={`${DOCS_URL}/guides/storage/uploads/file-limits`}
>
Learn more
</InlineLink>
.
{!shouldAutoValidateBucketLimits && (
<p>
Ensure that the global limit is greater than that of
individual buckets
</p>
)}
</>
}
>
<FormControl>
<div className="flex items-center justify-end">
<Input
type="number"
{...field}
onChange={(e) => {
field.onChange(e)
form.clearErrors('fileSizeLimit')
}}
className="w-32 rounded-r-none border-r-0"
disabled={
!hasAccessToFileSizeConfiguration ||
!canUpdateStorageSettings
}
/>
<FormField
control={form.control}
name="unit"
render={({ field: unitField }) => (
<Select
value={unitField.value}
onValueChange={(val) => {
unitField.onChange(val)
form.clearErrors('fileSizeLimit')
}}
disabled={
!hasAccessToFileSizeConfiguration ||
!canUpdateStorageSettings
}
>
<SelectTrigger className="w-[90px] text-xs font-mono rounded-l-none bg-surface-300">
<SelectValue placeholder="Choose a prefix">
{storageUnit}
</SelectValue>
</SelectTrigger>
<SelectContent>
{Object.values(StorageSizeUnits).map((unit: string) => (
<SelectItem
key={unit}
disabled={!hasAccessToFileSizeConfiguration}
value={unit}
>
{unit}
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
</div>
</FormControl>
{sizeLimitCheckCondition === 'confirm' && (
<ValidateSizeLimit
onValidate={sizeLimitCheckQuery}
projectRef={projectRef}
isLoadingBucketEstimate={isBucketEstimatePending}
/>
)}
</FormItemLayout>
)}
/>
{fileSizeLimitError && (
<FormMessage className="ml-auto mt-2 text-right w-1/2">
<StorageFileSizeLimitErrorMessage
error={fileSizeLimitError}
projectRef={projectRef}
/>
</FormMessage>
)}
</CardContent>
{hasLimitedStorageAccess && (
<UpgradeToPro
fullWidth
variant="primary"
source="storageSizeLimit"
featureProposition="configure upload file size limits in Storage"
primaryText="Free Plan has a fixed upload file size limit of 50 MB"
secondaryText={`Upgrade to Pro Plan for a configurable upload file size limit of ${formatBytes(
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_UNCAPPED
)} and unlock image transformations.`}
/>
)}
{isSpendCapOn && (
<UpgradeToPro
fullWidth
addon="spendCap"
variant="default"
source="storageSizeLimit"
featureProposition="increase the file upload size limits in Storage"
buttonText="Disable spend cap"
primaryText="Reduced max upload file size limit due to spend cap"
secondaryText={`Disable your spend cap to allow file uploads of up to ${formatBytes(
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_UNCAPPED
)}.`}
/>
)}
{!canUpdateStorageSettings && (
<CardContent>
<p className="text-sm text-foreground-light">
You need additional permissions to update storage settings
</p>
</CardContent>
)}
<CardFooter className="justify-end space-x-2">
{form.formState.isDirty && (
<Button
variant="default"
type="reset"
onClick={() => form.reset()}
disabled={
!form.formState.isDirty || !canUpdateStorageSettings || isUpdating
<form id={formId} onSubmit={form.handleSubmit(onSubmit)}>
<Card>
<CardContent>
<FormField
control={form.control}
name="imageTransformationEnabled"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Enable image transformation"
description={
<>
Optimize and resize images on the fly.{' '}
<InlineLink
href={`${DOCS_URL}/guides/storage/serving/image-transformations`}
>
Learn more
</InlineLink>
.
</>
}
>
Cancel
</Button>
<FormControl>
<Switch
size="large"
disabled={
!hasAccessToImageTransformations || !canUpdateStorageSettings
}
checked={hasAccessToImageTransformations && field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItemLayout>
)}
/>
</CardContent>
<CardContent>
<FormField
control={form.control}
name="fileSizeLimit"
render={({ field }) => (
<FormItemLayout
hideMessage
layout="flex-row-reverse"
label="Global file size limit"
description={
<>
Restrict the size of files uploaded across all buckets.{' '}
<InlineLink
href={`${DOCS_URL}/guides/storage/uploads/file-limits`}
>
Learn more
</InlineLink>
.
{!shouldAutoValidateBucketLimits && (
<p>
Ensure that the global limit is greater than that of
individual buckets
</p>
)}
</>
}
>
<FormControl>
<div className="flex items-center justify-end">
<Input
type="number"
{...field}
onChange={(e) => {
field.onChange(e)
form.clearErrors('fileSizeLimit')
}}
className="w-32 rounded-r-none border-r-0"
disabled={
!hasAccessToFileSizeConfiguration || !canUpdateStorageSettings
}
/>
<FormField
control={form.control}
name="unit"
render={({ field: unitField }) => (
<Select
value={unitField.value}
onValueChange={(val) => {
unitField.onChange(val)
form.clearErrors('fileSizeLimit')
}}
disabled={
!hasAccessToFileSizeConfiguration ||
!canUpdateStorageSettings
}
>
<SelectTrigger className="w-[90px] text-xs font-mono rounded-l-none bg-surface-300">
<SelectValue placeholder="Choose a prefix">
{storageUnit}
</SelectValue>
</SelectTrigger>
<SelectContent>
{Object.values(StorageSizeUnits).map((unit: string) => (
<SelectItem
key={unit}
disabled={!hasAccessToFileSizeConfiguration}
value={unit}
>
{unit}
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
</div>
</FormControl>
{sizeLimitCheckCondition === 'confirm' && (
<ValidateSizeLimit
onValidate={sizeLimitCheckQuery}
projectRef={projectRef}
isLoadingBucketEstimate={isBucketEstimatePending}
/>
)}
</FormItemLayout>
)}
/>
{fileSizeLimitError && (
<FormMessage className="ml-auto mt-2 text-right w-1/2">
<StorageFileSizeLimitErrorMessage
error={fileSizeLimitError}
projectRef={projectRef}
/>
</FormMessage>
)}
</CardContent>
{hasLimitedStorageAccess && (
<UpgradeToPro
fullWidth
variant="primary"
source="storageSizeLimit"
featureProposition="configure upload file size limits in Storage"
primaryText="Free Plan has a fixed upload file size limit of 50 MB"
secondaryText={`Upgrade to Pro Plan for a configurable upload file size limit of ${formatBytes(
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_UNCAPPED
)} and unlock image transformations.`}
/>
)}
{isSpendCapOn && (
<UpgradeToPro
fullWidth
addon="spendCap"
variant="default"
source="storageSizeLimit"
featureProposition="increase the file upload size limits in Storage"
buttonText="Disable spend cap"
primaryText="Reduced max upload file size limit due to spend cap"
secondaryText={`Disable your spend cap to allow file uploads of up to ${formatBytes(
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_UNCAPPED
)}.`}
/>
)}
{!canUpdateStorageSettings && (
<CardContent>
<p className="text-sm text-foreground-light">
You need additional permissions to update storage settings
</p>
</CardContent>
)}
<CardFooter className="justify-end space-x-2">
{form.formState.isDirty && (
<Button
variant={hasLimitedStorageAccess ? 'default' : 'primary'}
type="submit"
loading={isUpdating}
variant="default"
type="reset"
onClick={() => form.reset()}
disabled={
!canUpdateStorageSettings || isUpdating || !form.formState.isDirty
!form.formState.isDirty || !canUpdateStorageSettings || isUpdating
}
>
Save
Cancel
</Button>
</CardFooter>
</Card>
</form>
</>
)}
<Button
variant={hasLimitedStorageAccess ? 'default' : 'primary'}
type="submit"
loading={isUpdating}
disabled={
!canUpdateStorageSettings || isUpdating || !form.formState.isDirty
}
>
Save
</Button>
</CardFooter>
</Card>
</form>
)}
</>
)}
@@ -1,4 +1,3 @@
import { useFlag } from 'common'
import { FlaskConical, Loader2, ScrollText, User2 } from 'lucide-react'
import { useTheme } from 'next-themes'
import Link from 'next/link'
@@ -42,7 +41,6 @@ export function UserDropdown({
const { theme, setTheme } = useTheme()
const appStateSnapshot = useAppStateSnapshot()
const profileShowEmailEnabled = useIsFeatureEnabled('profile:show_email')
const timezonePickerEnabled = useFlag('timezonePicker')
const { username, avatarUrl, primaryEmail, isLoading } = useProfileNameAndPicture()
const { toggleFeaturePreviewModal } = useFeaturePreviewModal()
@@ -105,7 +103,9 @@ export function UserDropdown({
</span>
)}
</div>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem className="flex gap-2 cursor-pointer" asChild>
<Link
@@ -141,6 +141,7 @@ export function UserDropdown({
</DropdownMenuGroup>
</>
)}
<DropdownMenuGroup>
<DropdownMenuLabel>Theme</DropdownMenuLabel>
<DropdownMenuRadioGroup
@@ -160,14 +161,13 @@ export function UserDropdown({
))}
</DropdownMenuRadioGroup>
</DropdownMenuGroup>
{timezonePickerEnabled && (
<>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<TimezoneDropdown />
</DropdownMenuGroup>
</>
)}
<DropdownMenuSeparator />
<DropdownMenuGroup>
<TimezoneDropdown />
</DropdownMenuGroup>
{showUpgradeCta && (
<>
<DropdownMenuSeparator />
@@ -23,8 +23,9 @@ import { useTrack } from '@/lib/telemetry/track'
const AUTO_OPTION_VALUE = '__auto__'
export const TimezoneDropdown = () => {
const { timezone, storedTimezone, setTimezone, isAutoDetected } = useTimezone()
const track = useTrack()
const { timezone, storedTimezone, setTimezone, isAutoDetected } = useTimezone()
const [open, setOpen] = useState(false)
// The "Auto detect" row always advertises the browser's own timezone, even
+4 -3
View File
@@ -107,7 +107,6 @@ const Wizard: NextPageWithLayout = () => {
)
const { hasLoaded: flagsLoaded } = useFeatureFlags()
const smartRegionEnabled = useFlag('enableSmartRegion')
const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate')
const showInternalOnlyConfiguration = useFlag('newProjectInternalOnlyConfiguration')
@@ -172,6 +171,7 @@ const Wizard: NextPageWithLayout = () => {
} = useWatch({ control: form.control })
const { dirtyFields } = useFormState(form)
const isDbRegionDirty = dirtyFields.dbRegion
const smartRegionEnabled = cloudProvider !== 'AWS_NIMBUS'
// Read dirty state during render rather than depending on form.formState in the
// effect — form.formState is a Proxy that gets a new reference every render, which
@@ -221,9 +221,10 @@ const Wizard: NextPageWithLayout = () => {
? 0
: monthlyInstancePrice(instanceSize) - availableComputeCredits
const selectedCloudProvider = cloudProvider as CloudProvider
const { data: autoDefaultRegion, error: defaultRegionError } = useDefaultRegionQuery(
{
cloudProvider: PROVIDERS[defaultProvider].id,
cloudProvider: selectedCloudProvider,
},
{
enabled: flagsLoaded && !smartRegionEnabled,
@@ -255,7 +256,7 @@ const Wizard: NextPageWithLayout = () => {
? availableRegionsData?.recommendations.smartGroup.name
: ''
const fixedDefaultRegion = PROVIDERS[defaultProvider].default_region.displayName
const fixedDefaultRegion = PROVIDERS[selectedCloudProvider].default_region.displayName
const regionError =
smartRegionEnabled && defaultProvider !== 'AWS_NIMBUS'
? availableRegionsError
@@ -1,4 +1,3 @@
import { useFlag } from 'common'
import { PageContainer } from 'ui-patterns/PageContainer'
import { AuthProvidersForm } from '@/components/interfaces/Auth/AuthProvidersForm'
@@ -12,13 +11,12 @@ import type { NextPageWithLayout } from '@/types'
const ProvidersPage: NextPageWithLayout = () => {
const showProviders = useIsFeatureEnabled('authentication:show_providers')
const showCustomProviders = useIsFeatureEnabled('authentication:show_custom_providers')
const isOauthProvidersEnabled = useFlag('CustomOauthProviders')
return (
<PageContainer size="default">
<BasicAuthSettingsForm />
{showProviders && <AuthProvidersForm />}
{showCustomProviders && isOauthProvidersEnabled && <CustomAuthProviders />}
{showCustomProviders && <CustomAuthProviders />}
</PageContainer>
)
}