mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Chore/cleanup feature flags 041224 (#30885)
* Deprecate enableFreeSupport flag * Deprecate enableExperimentalTheme flag and ThemeSettingsOld component * Deprecate httpsAuthHooksEnabled flag * Deprecate thirdPartyAuth flag * Deprecate logdrains flag * Deprecate enableNewRegions flag * Deprecate cronUi flag * Deprecate queues flag and old ProjectIntegrationsLayout folder
This commit is contained in:
1 parent
d265797541
commit
fdf4dec344
12 files changed
+265
-614
No files matched your search
@@ -1,54 +0,0 @@
|
||||
import { Moon, Sun } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { Listbox, Theme, themes } from 'ui'
|
||||
|
||||
const ThemeSettings = () => {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const { theme, resolvedTheme, setTheme } = useTheme()
|
||||
|
||||
/**
|
||||
* Avoid Hydration Mismatch
|
||||
* https://github.com/pacocoursey/next-themes?tab=readme-ov-file#avoid-hydration-mismatch
|
||||
*/
|
||||
// useEffect only runs on the client, so now we can safely show the UI
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
if (!mounted) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel title={<h5 key="panel-title">Theme</h5>}>
|
||||
<Panel.Content>
|
||||
<Listbox
|
||||
value={theme}
|
||||
label="Interface theme"
|
||||
descriptionText="Choose a theme preference"
|
||||
layout="horizontal"
|
||||
style={{ width: '50%' }}
|
||||
icon={resolvedTheme === 'dark' ? <Moon /> : <Sun />}
|
||||
onChange={(value: string) => setTheme(value)}
|
||||
>
|
||||
{themes
|
||||
.filter(
|
||||
(theme: Theme) =>
|
||||
// temporary fix to exclude new themes
|
||||
theme.value === 'light' || theme.value === 'dark' || theme.value === 'system'
|
||||
)
|
||||
.map((theme: Theme) => (
|
||||
<Listbox.Option key={theme.value} label={theme.name} value={theme.value}>
|
||||
{theme.name}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemeSettings
|
||||
@@ -1,4 +1,3 @@
|
||||
export { default as AccountInformation } from './AccountInformation'
|
||||
export { default as AnalyticsSettings } from './AnalyticsSettings'
|
||||
export { default as ThemeSettingsOld } from './ThemeSettingsOld'
|
||||
export { default as ThemeSettings } from './ThemeSettings'
|
||||
@@ -16,7 +16,6 @@ import SchemaSelector from 'components/ui/SchemaSelector'
|
||||
import { AuthConfigResponse } from 'data/auth/auth-config-query'
|
||||
import { useAuthHooksUpdateMutation } from 'data/auth/auth-hooks-update-mutation'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { useFlag } from 'hooks/ui/useFlag'
|
||||
import {
|
||||
Button,
|
||||
FormControl_Shadcn_,
|
||||
@@ -116,7 +115,6 @@ export const CreateHookSheet = ({
|
||||
}: CreateHookSheetProps) => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const { project } = useProjectContext()
|
||||
const httpsAuthHooksEnabled = useFlag('httpsAuthHooksEnabled')
|
||||
|
||||
const definition = useMemo(
|
||||
() => HOOKS_DEFINITIONS.find((d) => d.title === title) || HOOKS_DEFINITIONS[0],
|
||||
@@ -326,37 +324,35 @@ export const CreateHookSheet = ({
|
||||
)}
|
||||
/>
|
||||
<Separator />
|
||||
{httpsAuthHooksEnabled && (
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="selectedType"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout label="Hook type" className="px-5">
|
||||
<FormControl_Shadcn_>
|
||||
<RadioGroupStacked
|
||||
value={field.value}
|
||||
onValueChange={(value) => field.onChange(value)}
|
||||
>
|
||||
<RadioGroupStackedItem
|
||||
value="postgres"
|
||||
id="postgres"
|
||||
key="postgres"
|
||||
label="Postgres"
|
||||
description="Used to call a Postgres function."
|
||||
/>
|
||||
<RadioGroupStackedItem
|
||||
value="https"
|
||||
id="https"
|
||||
key="https"
|
||||
label="HTTPS"
|
||||
description="Used to call any HTTPS endpoint."
|
||||
/>
|
||||
</RadioGroupStacked>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="selectedType"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout label="Hook type" className="px-5">
|
||||
<FormControl_Shadcn_>
|
||||
<RadioGroupStacked
|
||||
value={field.value}
|
||||
onValueChange={(value) => field.onChange(value)}
|
||||
>
|
||||
<RadioGroupStackedItem
|
||||
value="postgres"
|
||||
id="postgres"
|
||||
key="postgres"
|
||||
label="Postgres"
|
||||
description="Used to call a Postgres function."
|
||||
/>
|
||||
<RadioGroupStackedItem
|
||||
value="https"
|
||||
id="https"
|
||||
key="https"
|
||||
label="HTTPS"
|
||||
description="Used to call any HTTPS endpoint."
|
||||
/>
|
||||
</RadioGroupStacked>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
{values.selectedType === 'postgres' ? (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-8 px-5">
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
useThirdPartyAuthIntegrationsQuery,
|
||||
} from 'data/third-party-auth/integrations-query'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { useFlag } from 'hooks/ui/useFlag'
|
||||
import { cn } from 'ui'
|
||||
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
||||
import { AddIntegrationDropdown } from './AddIntegrationDropdown'
|
||||
@@ -27,7 +26,6 @@ import {
|
||||
} from './ThirdPartyAuthForm.utils'
|
||||
|
||||
export const ThirdPartyAuthForm = () => {
|
||||
const thirdPartyAuthEnabled = useFlag('thirdPartyAuth')
|
||||
const { ref: projectRef } = useParams()
|
||||
const {
|
||||
data: integrationsData,
|
||||
@@ -45,10 +43,6 @@ export const ThirdPartyAuthForm = () => {
|
||||
const { mutateAsync: deleteIntegration } = useDeleteThirdPartyAuthIntegrationMutation()
|
||||
const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue')
|
||||
|
||||
if (!thirdPartyAuthEnabled) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<AlertError
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useRouter } from 'next/router'
|
||||
import { ControllerRenderProps, UseFormReturn } from 'react-hook-form'
|
||||
|
||||
import { useDefaultRegionQuery } from 'data/misc/get-default-region-query'
|
||||
import { useFlag } from 'hooks/ui/useFlag'
|
||||
import { PROVIDERS } from 'lib/constants'
|
||||
import type { CloudProvider } from 'shared-data'
|
||||
import {
|
||||
@@ -28,17 +27,13 @@ interface RegionSelectorProps {
|
||||
|
||||
export const RegionSelector = ({ cloudProvider, field }: RegionSelectorProps) => {
|
||||
const router = useRouter()
|
||||
const newRegions = ['EAST_US_2', 'WEST_EU_3', 'CENTRAL_EU_2', 'NORTH_EU']
|
||||
const enableNewRegions = useFlag('enableNewRegions')
|
||||
|
||||
const showNonProdFields =
|
||||
process.env.NEXT_PUBLIC_ENVIRONMENT === 'local' ||
|
||||
process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'
|
||||
|
||||
const availableRegions = getAvailableRegions(PROVIDERS[cloudProvider].id)
|
||||
const regionsArray = enableNewRegions
|
||||
? Object.entries(availableRegions)
|
||||
: Object.entries(availableRegions).filter(([key]) => !newRegions.includes(key))
|
||||
const regionsArray = Object.entries(availableRegions)
|
||||
|
||||
const { isLoading: isLoadingDefaultRegion } = useDefaultRegionQuery({
|
||||
cloudProvider,
|
||||
|
||||
@@ -10,12 +10,13 @@ import * as z from 'zod'
|
||||
|
||||
import { useDocsSearch, useParams, type DocsSearchResult as Page } from 'common'
|
||||
import { CLIENT_LIBRARIES } from 'common/constants'
|
||||
import { getProjectAuthConfig } from 'data/auth/auth-config-query'
|
||||
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 { useFlag } from 'hooks/ui/useFlag'
|
||||
import { detectBrowser } from 'lib/helpers'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import {
|
||||
Badge,
|
||||
@@ -37,8 +38,10 @@ import {
|
||||
TextArea_Shadcn_,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import DisabledStateForFreeTier from './DisabledStateForFreeTier'
|
||||
import { MultiSelectV2 } from 'ui-patterns/MultiSelectDeprecated/MultiSelectV2'
|
||||
import { DocsLinkGroup } from './DocsLink'
|
||||
import { IPV4SuggestionAlert } from './IPV4SuggestionAlert'
|
||||
import { LibrarySuggestions } from './LibrarySuggestions'
|
||||
import { PlanExpectationInfoBox } from './PlanExpectationInfoBox'
|
||||
import {
|
||||
CATEGORY_OPTIONS,
|
||||
@@ -46,12 +49,7 @@ import {
|
||||
SERVICE_OPTIONS,
|
||||
SEVERITY_OPTIONS,
|
||||
} from './Support.constants'
|
||||
import { LibrarySuggestions } from './LibrarySuggestions'
|
||||
import { MultiSelectV2 } from 'ui-patterns/MultiSelectDeprecated/MultiSelectV2'
|
||||
import { IPV4SuggestionAlert } from './IPV4SuggestionAlert'
|
||||
import { formatMessage, uploadAttachments } from './SupportForm.utils'
|
||||
import { detectBrowser } from 'lib/helpers'
|
||||
import { getProjectAuthConfig } from 'data/auth/auth-config-query'
|
||||
|
||||
const MAX_ATTACHMENTS = 5
|
||||
const INCLUDE_DISCUSSIONS = ['Problem', 'Database_unresponsive']
|
||||
@@ -68,7 +66,6 @@ export const SupportFormV2 = ({ setSentCategory, setSelectedProject }: SupportFo
|
||||
const { profile } = useProfile()
|
||||
const supabaseClient = useSupabaseClient()
|
||||
const { ref, slug, category: urlCategory, subject: urlSubject, message: urlMessage } = useParams()
|
||||
const enableFreeSupport = useFlag('enableFreeSupport')
|
||||
|
||||
const uploadButtonRef = useRef(null)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
@@ -486,250 +483,239 @@ export const SupportFormV2 = ({ setSentCategory, setSelectedProject }: SupportFo
|
||||
|
||||
<Separator />
|
||||
|
||||
{!enableFreeSupport || (category === 'Performance' && subscriptionPlanId === 'free') ? (
|
||||
<DisabledStateForFreeTier
|
||||
category={CATEGORY_OPTIONS.find((o) => o.value === category)?.label ?? ''}
|
||||
organizationSlug={organizationSlug}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className={cn(CONTAINER_CLASSES, 'flex flex-col gap-y-2')}>
|
||||
<FormField_Shadcn_
|
||||
name="subject"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="vertical"
|
||||
label="Subject"
|
||||
description={
|
||||
field.value.length > 0 &&
|
||||
INCLUDE_DISCUSSIONS.includes(category) && (
|
||||
<p className="flex items-center gap-x-1">
|
||||
<span>Check our </span>
|
||||
<Link
|
||||
key="gh-discussions"
|
||||
href={`https://github.com/orgs/supabase/discussions?discussions_q=${field.value}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex items-center gap-x-1 text-foreground-light underline hover:text-foreground transition"
|
||||
>
|
||||
Github discussions
|
||||
<ExternalLink size={14} strokeWidth={2} />
|
||||
</Link>
|
||||
<span> for a quick answer</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_ {...field} placeholder="Summary of the problem you have" />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
{docsResults.length > 0 && hasResults && (
|
||||
<div className="pt-4 px-4 border rounded-md">
|
||||
<h2 className="text-sm text-foreground-light px-2 mb-4">
|
||||
Suggested resources ({Math.min(docsResults.length, 5)})
|
||||
</h2>
|
||||
<ScrollArea className={docsResults.length > 3 ? 'h-[300px]' : ''}>
|
||||
{docsResults.slice(0, 5).map((page, i) => (
|
||||
<DocsLinkGroup key={`${page.id}-group`} page={page} />
|
||||
))}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{category === 'Problem' && (
|
||||
<FormField_Shadcn_
|
||||
name="library"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
className={cn(CONTAINER_CLASSES)}
|
||||
layout="vertical"
|
||||
label="Which library are you having issues with"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Select_Shadcn_
|
||||
{...field}
|
||||
defaultValue={field.value}
|
||||
onValueChange={field.onChange}
|
||||
<div className={cn(CONTAINER_CLASSES, 'flex flex-col gap-y-2')}>
|
||||
<FormField_Shadcn_
|
||||
name="subject"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="vertical"
|
||||
label="Subject"
|
||||
description={
|
||||
field.value.length > 0 &&
|
||||
INCLUDE_DISCUSSIONS.includes(category) && (
|
||||
<p className="flex items-center gap-x-1">
|
||||
<span>Check our </span>
|
||||
<Link
|
||||
key="gh-discussions"
|
||||
href={`https://github.com/orgs/supabase/discussions?discussions_q=${field.value}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex items-center gap-x-1 text-foreground-light underline hover:text-foreground transition"
|
||||
>
|
||||
<SelectTrigger_Shadcn_ className="w-full">
|
||||
<SelectValue_Shadcn_ placeholder="Please select a library" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectGroup_Shadcn_>
|
||||
{CLIENT_LIBRARIES.map((option) => (
|
||||
<SelectItem_Shadcn_ value={option.language}>
|
||||
{option.language}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectGroup_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
Github discussions
|
||||
<ExternalLink size={14} strokeWidth={2} />
|
||||
</Link>
|
||||
<span> for a quick answer</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_ {...field} placeholder="Summary of the problem you have" />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
{library.length > 0 && <LibrarySuggestions library={library} />}
|
||||
|
||||
{category !== 'Login_issues' && (
|
||||
<FormField_Shadcn_
|
||||
name="affectedServices"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
className={cn(CONTAINER_CLASSES)}
|
||||
layout="vertical"
|
||||
label="Which services are affected?"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<MultiSelectV2
|
||||
options={SERVICE_OPTIONS}
|
||||
value={field.value.length === 0 ? [] : field.value?.split(', ')}
|
||||
placeholder="No particular service"
|
||||
searchPlaceholder="Search for a service"
|
||||
onChange={(services) =>
|
||||
form.setValue('affectedServices', services.join(', '))
|
||||
}
|
||||
/>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField_Shadcn_
|
||||
name="message"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
className={cn(CONTAINER_CLASSES)}
|
||||
layout="vertical"
|
||||
label="Message"
|
||||
labelOptional="5000 character limit"
|
||||
description={
|
||||
IPV4_MIGRATION_STRINGS.some((str) => field.value.includes(str)) && (
|
||||
<IPV4SuggestionAlert />
|
||||
)
|
||||
}
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<TextArea_Shadcn_
|
||||
{...field}
|
||||
rows={4}
|
||||
maxLength={5000}
|
||||
placeholder="Describe the issue you're facing, along with any relevant information. Please be as detailed and specific as possible."
|
||||
/>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
{['Problem', 'Database_unresponsive', 'Performance'].includes(category) && (
|
||||
<FormField_Shadcn_
|
||||
name="allowSupportAccess"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="flex"
|
||||
className={cn(CONTAINER_CLASSES)}
|
||||
label="Allow Supabase Support to access your project temporarily"
|
||||
description="In some cases, we may require temporary access to your project to complete troubleshooting, or to answer questions related specifically to your project"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Checkbox_Shadcn_
|
||||
{...field}
|
||||
value={String(field.value)}
|
||||
checked={field.value}
|
||||
onCheckedChange={(value) => field.onChange(value)}
|
||||
/>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={cn(CONTAINER_CLASSES)}>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<p className="text-sm text-foreground-light">Attachments</p>
|
||||
<p className="text-sm text-foreground-lighter">
|
||||
Upload up to {MAX_ATTACHMENTS} screenshots that might be relevant to the issue
|
||||
that you're facing
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
multiple
|
||||
type="file"
|
||||
ref={uploadButtonRef}
|
||||
className="hidden"
|
||||
accept="image/png, image/jpeg"
|
||||
onChange={onFilesUpload}
|
||||
/>
|
||||
<div className="flex items-center gap-x-2 mt-4">
|
||||
{uploadedDataUrls.map((x: any, idx: number) => (
|
||||
<div
|
||||
key={idx}
|
||||
style={{ backgroundImage: `url("${x}")` }}
|
||||
className="relative h-14 w-14 rounded bg-cover bg-center bg-no-repeat"
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
'flex h-4 w-4 items-center justify-center rounded-full bg-red-900',
|
||||
'absolute -top-1 -right-1 cursor-pointer',
|
||||
].join(' ')}
|
||||
onClick={() => removeUploadedFile(idx)}
|
||||
>
|
||||
<X size={12} strokeWidth={2} />
|
||||
</div>
|
||||
</div>
|
||||
{docsResults.length > 0 && hasResults && (
|
||||
<div className="pt-4 px-4 border rounded-md">
|
||||
<h2 className="text-sm text-foreground-light px-2 mb-4">
|
||||
Suggested resources ({Math.min(docsResults.length, 5)})
|
||||
</h2>
|
||||
<ScrollArea className={docsResults.length > 3 ? 'h-[300px]' : ''}>
|
||||
{docsResults.slice(0, 5).map((page, i) => (
|
||||
<DocsLinkGroup key={`${page.id}-group`} page={page} />
|
||||
))}
|
||||
{uploadedFiles.length < MAX_ATTACHMENTS && (
|
||||
<div
|
||||
className={[
|
||||
'border border-stronger opacity-50 transition hover:opacity-100',
|
||||
'group flex h-14 w-14 cursor-pointer items-center justify-center rounded',
|
||||
].join(' ')}
|
||||
onClick={() => {
|
||||
if (uploadButtonRef.current) (uploadButtonRef.current as any).click()
|
||||
}}
|
||||
>
|
||||
<Plus strokeWidth={2} size={20} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={cn(CONTAINER_CLASSES)}>
|
||||
<div className="flex items-center space-x-1 justify-end block text-sm mt-0 mb-2">
|
||||
<p className="text-foreground-light">We will contact you at</p>
|
||||
<p className="text-foreground font-medium">{respondToEmail}</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1 justify-end block text-sm mt-0 mb-2">
|
||||
<p className="text-foreground-light">
|
||||
Please ensure you haven't blocked Hubspot in your emails
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
htmlType="submit"
|
||||
size="small"
|
||||
icon={<Mail />}
|
||||
disabled={isSubmitting}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Send support request
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
{category === 'Problem' && (
|
||||
<FormField_Shadcn_
|
||||
name="library"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
className={cn(CONTAINER_CLASSES)}
|
||||
layout="vertical"
|
||||
label="Which library are you having issues with"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Select_Shadcn_
|
||||
{...field}
|
||||
defaultValue={field.value}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<SelectTrigger_Shadcn_ className="w-full">
|
||||
<SelectValue_Shadcn_ placeholder="Please select a library" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectGroup_Shadcn_>
|
||||
{CLIENT_LIBRARIES.map((option) => (
|
||||
<SelectItem_Shadcn_ value={option.language}>
|
||||
{option.language}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectGroup_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{library.length > 0 && <LibrarySuggestions library={library} />}
|
||||
|
||||
{category !== 'Login_issues' && (
|
||||
<FormField_Shadcn_
|
||||
name="affectedServices"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
className={cn(CONTAINER_CLASSES)}
|
||||
layout="vertical"
|
||||
label="Which services are affected?"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<MultiSelectV2
|
||||
options={SERVICE_OPTIONS}
|
||||
value={field.value.length === 0 ? [] : field.value?.split(', ')}
|
||||
placeholder="No particular service"
|
||||
searchPlaceholder="Search for a service"
|
||||
onChange={(services) => form.setValue('affectedServices', services.join(', '))}
|
||||
/>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField_Shadcn_
|
||||
name="message"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
className={cn(CONTAINER_CLASSES)}
|
||||
layout="vertical"
|
||||
label="Message"
|
||||
labelOptional="5000 character limit"
|
||||
description={
|
||||
IPV4_MIGRATION_STRINGS.some((str) => field.value.includes(str)) && (
|
||||
<IPV4SuggestionAlert />
|
||||
)
|
||||
}
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<TextArea_Shadcn_
|
||||
{...field}
|
||||
rows={4}
|
||||
maxLength={5000}
|
||||
placeholder="Describe the issue you're facing, along with any relevant information. Please be as detailed and specific as possible."
|
||||
/>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
|
||||
{['Problem', 'Database_unresponsive', 'Performance'].includes(category) && (
|
||||
<FormField_Shadcn_
|
||||
name="allowSupportAccess"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="flex"
|
||||
className={cn(CONTAINER_CLASSES)}
|
||||
label="Allow Supabase Support to access your project temporarily"
|
||||
description="In some cases, we may require temporary access to your project to complete troubleshooting, or to answer questions related specifically to your project"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Checkbox_Shadcn_
|
||||
{...field}
|
||||
value={String(field.value)}
|
||||
checked={field.value}
|
||||
onCheckedChange={(value) => field.onChange(value)}
|
||||
/>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={cn(CONTAINER_CLASSES)}>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<p className="text-sm text-foreground-light">Attachments</p>
|
||||
<p className="text-sm text-foreground-lighter">
|
||||
Upload up to {MAX_ATTACHMENTS} screenshots that might be relevant to the issue that
|
||||
you're facing
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
multiple
|
||||
type="file"
|
||||
ref={uploadButtonRef}
|
||||
className="hidden"
|
||||
accept="image/png, image/jpeg"
|
||||
onChange={onFilesUpload}
|
||||
/>
|
||||
<div className="flex items-center gap-x-2 mt-4">
|
||||
{uploadedDataUrls.map((x: any, idx: number) => (
|
||||
<div
|
||||
key={idx}
|
||||
style={{ backgroundImage: `url("${x}")` }}
|
||||
className="relative h-14 w-14 rounded bg-cover bg-center bg-no-repeat"
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
'flex h-4 w-4 items-center justify-center rounded-full bg-red-900',
|
||||
'absolute -top-1 -right-1 cursor-pointer',
|
||||
].join(' ')}
|
||||
onClick={() => removeUploadedFile(idx)}
|
||||
>
|
||||
<X size={12} strokeWidth={2} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{uploadedFiles.length < MAX_ATTACHMENTS && (
|
||||
<div
|
||||
className={[
|
||||
'border border-stronger opacity-50 transition hover:opacity-100',
|
||||
'group flex h-14 w-14 cursor-pointer items-center justify-center rounded',
|
||||
].join(' ')}
|
||||
onClick={() => {
|
||||
if (uploadButtonRef.current) (uploadButtonRef.current as any).click()
|
||||
}}
|
||||
>
|
||||
<Plus strokeWidth={2} size={20} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cn(CONTAINER_CLASSES)}>
|
||||
<div className="flex items-center space-x-1 justify-end block text-sm mt-0 mb-2">
|
||||
<p className="text-foreground-light">We will contact you at</p>
|
||||
<p className="text-foreground font-medium">{respondToEmail}</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1 justify-end block text-sm mt-0 mb-2">
|
||||
<p className="text-foreground-light">
|
||||
Please ensure you haven't blocked Hubspot in your emails
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
htmlType="submit"
|
||||
size="small"
|
||||
icon={<Mail />}
|
||||
disabled={isSubmitting}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Send support request
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form_Shadcn_>
|
||||
)
|
||||
|
||||
-107
@@ -1,107 +0,0 @@
|
||||
import { Code } from 'lucide-react'
|
||||
|
||||
import { COMMAND_MENU_SECTIONS } from 'components/interfaces/App/CommandMenu/CommandMenu.utils'
|
||||
import type { CommandOptions } from 'ui-patterns/CommandMenu'
|
||||
import { useRegisterCommands } from 'ui-patterns/CommandMenu'
|
||||
import { useParams } from 'common'
|
||||
import { orderCommandSectionsByPriority } from 'components/interfaces/App/CommandMenu/ordering'
|
||||
|
||||
export function useDatabaseGotoCommands(options?: CommandOptions) {
|
||||
let { ref } = useParams()
|
||||
ref ||= '_'
|
||||
|
||||
useRegisterCommands(
|
||||
COMMAND_MENU_SECTIONS.QUERY,
|
||||
[
|
||||
{
|
||||
id: 'run-sql',
|
||||
name: 'Run SQL',
|
||||
route: `/project/${ref}/sql/new`,
|
||||
icon: () => <Code />,
|
||||
},
|
||||
],
|
||||
{
|
||||
...options,
|
||||
deps: [ref],
|
||||
orderSection: orderCommandSectionsByPriority,
|
||||
sectionMeta: { priority: 2 },
|
||||
}
|
||||
)
|
||||
|
||||
useRegisterCommands(
|
||||
COMMAND_MENU_SECTIONS.NAVIGATE,
|
||||
[
|
||||
{
|
||||
id: 'nav-database-tables',
|
||||
name: 'Tables',
|
||||
value: 'Database: Tables',
|
||||
route: `/project/${ref}/database/tables`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-database-triggers',
|
||||
name: 'Triggers',
|
||||
value: 'Database: Triggers',
|
||||
route: `/project/${ref}/database/triggers`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-database-functions',
|
||||
name: 'Functions',
|
||||
value: 'Database: Functions',
|
||||
route: `/project/${ref}/database/functions`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-database-extensions',
|
||||
name: 'Extensions',
|
||||
value: 'Database: Extensions',
|
||||
route: `/project/${ref}/database/extensions`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-database-roles',
|
||||
name: 'Roles',
|
||||
value: 'Database: Roles',
|
||||
route: `/project/${ref}/database/roles`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-database-replication',
|
||||
name: 'Replication',
|
||||
value: 'Database: Replication',
|
||||
route: `/project/${ref}/database/replication`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-database-hooks',
|
||||
name: 'Webhooks',
|
||||
value: 'Database: Webhooks',
|
||||
route: `/project/${ref}/integrations/hooks`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-database-backups',
|
||||
name: 'Backups',
|
||||
value: 'Database: Backups',
|
||||
route: `/project/${ref}/database/backups/scheduled`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-database-wrappers',
|
||||
name: 'Wrappers',
|
||||
value: 'Database: Wrappers',
|
||||
route: `/project/${ref}/integrations/wrappers`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-database-migrations',
|
||||
name: 'Migrations',
|
||||
value: 'Database: Migrations',
|
||||
route: `/project/${ref}/database/migrations`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
],
|
||||
{ ...options, deps: [ref] }
|
||||
)
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
import { IS_PLATFORM } from 'common'
|
||||
import { ProductMenu } from 'components/ui/ProductMenu'
|
||||
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
|
||||
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
|
||||
import { withAuth } from 'hooks/misc/withAuth'
|
||||
import { useFlag } from 'hooks/ui/useFlag'
|
||||
import ProjectLayout from '../ProjectLayout/ProjectLayout'
|
||||
import { generateProjectIntegrationsMenu } from './ProjectIntegrationsMenu.utils'
|
||||
|
||||
export interface ProjectIntegrationsLayoutProps {
|
||||
title: string
|
||||
}
|
||||
|
||||
const ProjectIntegrationsMenu = () => {
|
||||
// if running on self-hosted, cron UI should be always enabled
|
||||
const cronUiEnabled = useFlag('cronUi') || !IS_PLATFORM
|
||||
const queuesUiEnabled = useFlag('queues')
|
||||
const project = useSelectedProject()
|
||||
|
||||
const router = useRouter()
|
||||
const page = router.pathname.split('/')[4]
|
||||
|
||||
const { data } = useDatabaseExtensionsQuery({
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
})
|
||||
|
||||
const pgNetExtensionExists = (data ?? []).find((ext) => ext.name === 'pg_net') !== undefined
|
||||
const graphqlExtensionExists = (data ?? []).find((ext) => ext.name === 'pg_graphql') !== undefined
|
||||
const pgmqExtensionExists = (data ?? []).find((ext) => ext.name === 'pgmq') !== undefined
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProductMenu
|
||||
page={page}
|
||||
menu={generateProjectIntegrationsMenu(project, {
|
||||
pgNetExtensionExists,
|
||||
cronUiEnabled,
|
||||
queuesUiEnabled,
|
||||
graphqlExtensionExists,
|
||||
pgmqExtensionExists,
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const ProjectIntegrationsLayout = ({
|
||||
children,
|
||||
title,
|
||||
}: PropsWithChildren<ProjectIntegrationsLayoutProps>) => {
|
||||
return (
|
||||
<ProjectLayout
|
||||
title={title}
|
||||
product="Integrations"
|
||||
productMenu={<ProjectIntegrationsMenu />}
|
||||
isBlocking={false}
|
||||
>
|
||||
{children}
|
||||
</ProjectLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(ProjectIntegrationsLayout)
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
import type { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types'
|
||||
import type { Project } from 'data/projects/project-detail-query'
|
||||
|
||||
export const generateProjectIntegrationsMenu = (
|
||||
project?: Project,
|
||||
flags?: {
|
||||
pgNetExtensionExists: boolean
|
||||
cronUiEnabled: boolean
|
||||
queuesUiEnabled: boolean
|
||||
graphqlExtensionExists: boolean
|
||||
pgmqExtensionExists: boolean
|
||||
}
|
||||
): ProductMenuGroup[] => {
|
||||
const ref = project?.ref ?? 'default'
|
||||
const {
|
||||
pgNetExtensionExists,
|
||||
cronUiEnabled,
|
||||
queuesUiEnabled,
|
||||
graphqlExtensionExists,
|
||||
pgmqExtensionExists,
|
||||
} = flags || {}
|
||||
|
||||
return [
|
||||
{
|
||||
title: 'Manage',
|
||||
items: [
|
||||
{
|
||||
name: 'Wrappers',
|
||||
key: 'wrappers',
|
||||
url: `/project/${ref}/integrations/wrappers`,
|
||||
items: [],
|
||||
},
|
||||
...(!!pgNetExtensionExists
|
||||
? [
|
||||
{
|
||||
name: 'Webhooks',
|
||||
key: 'hooks',
|
||||
url: `/project/${ref}/integrations/hooks`,
|
||||
items: [],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
...(!!cronUiEnabled
|
||||
? [
|
||||
{
|
||||
name: 'Cron',
|
||||
key: 'cron-jobs',
|
||||
url: `/project/${ref}/integrations/cron`,
|
||||
items: [],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!!graphqlExtensionExists
|
||||
? [
|
||||
{
|
||||
name: 'GraphiQL',
|
||||
key: 'graphiql',
|
||||
url: `/project/${ref}/integrations/graphiql`,
|
||||
items: [],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: 'Vault',
|
||||
key: 'vault',
|
||||
url: `/project/${ref}/integrations/vault/secrets`,
|
||||
items: [],
|
||||
label: 'BETA',
|
||||
},
|
||||
...(!!(queuesUiEnabled && pgmqExtensionExists)
|
||||
? [
|
||||
{
|
||||
name: 'Queues',
|
||||
key: 'queues',
|
||||
url: `/project/${ref}/integrations/queues`,
|
||||
items: [],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -47,7 +47,6 @@ const SettingsLayout = ({ title, children }: PropsWithChildren<SettingsLayoutPro
|
||||
])
|
||||
|
||||
const warehouseEnabled = useFlag('warehouse')
|
||||
const logDrainsEnabled = useFlag('logdrains')
|
||||
const diskAndComputeEnabled = useFlag('diskAndComputeForm')
|
||||
|
||||
const menuRoutes = generateSettingsMenu(ref, project, organization, {
|
||||
@@ -56,7 +55,6 @@ const SettingsLayout = ({ title, children }: PropsWithChildren<SettingsLayoutPro
|
||||
storage: storageEnabled,
|
||||
invoices: invoicesEnabled,
|
||||
warehouse: warehouseEnabled,
|
||||
logDrains: logDrainsEnabled,
|
||||
diskAndCompute: diskAndComputeEnabled,
|
||||
})
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ export const generateSettingsMenu = (
|
||||
storage?: boolean
|
||||
invoices?: boolean
|
||||
warehouse?: boolean
|
||||
logDrains?: boolean
|
||||
diskAndCompute?: boolean
|
||||
}
|
||||
): ProductMenuGroup[] => {
|
||||
@@ -26,7 +25,6 @@ export const generateSettingsMenu = (
|
||||
const edgeFunctionsEnabled = features?.edgeFunctions ?? true
|
||||
const storageEnabled = features?.storage ?? true
|
||||
const warehouseEnabled = features?.warehouse ?? false
|
||||
const logDrainsEnabled = features?.logDrains ?? false
|
||||
const newDiskComputeEnabled = features?.diskAndCompute ?? false
|
||||
|
||||
return [
|
||||
@@ -136,7 +134,7 @@ export const generateSettingsMenu = (
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(IS_PLATFORM && logDrainsEnabled
|
||||
...(IS_PLATFORM
|
||||
? [
|
||||
{
|
||||
name: `Log Drains`,
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
AccountInformation,
|
||||
AnalyticsSettings,
|
||||
ThemeSettings,
|
||||
ThemeSettingsOld,
|
||||
} from 'components/interfaces/Account/Preferences'
|
||||
import { AccountDeletion } from 'components/interfaces/Account/Preferences/AccountDeletion'
|
||||
import { ProfileInformation } from 'components/interfaces/Account/Preferences/ProfileInformation'
|
||||
@@ -11,7 +10,6 @@ import AlertError from 'components/ui/AlertError'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import { useFlag } from 'hooks/ui/useFlag'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
|
||||
@@ -41,11 +39,8 @@ export default User
|
||||
|
||||
const ProfileCard = () => {
|
||||
const profileUpdateEnabled = useIsFeatureEnabled('profile:update')
|
||||
|
||||
const { profile, error, isLoading, isError, isSuccess } = useProfile()
|
||||
|
||||
const experimentalThemeEnabled = useFlag('enableExperimentalTheme')
|
||||
|
||||
return (
|
||||
<article className="max-w-4xl p-4">
|
||||
{isLoading && (
|
||||
@@ -71,7 +66,9 @@ const ProfileCard = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<section>{experimentalThemeEnabled ? <ThemeSettings /> : <ThemeSettingsOld />}</section>
|
||||
<section>
|
||||
<ThemeSettings />
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<AnalyticsSettings />
|
||||
|
||||
Reference in new issue
Block a user