Skip to content

Commit 607dafe

Browse files
authored
Remove useProjectsQuery from support form (supabase#39026)
* Remove useProjectsQuery from support form * Add comment for refactor
1 parent 8d028b8 commit 607dafe

File tree

3 files changed

+140
-121
lines changed

3 files changed

+140
-121
lines changed

apps/studio/components/interfaces/Support/Success.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,24 @@ import { Check, ExternalLink, Mail, Search } from 'lucide-react'
22
import Link from 'next/link'
33
import { useState } from 'react'
44

5+
import { useProjectDetailQuery } from 'data/projects/project-detail-query'
56
import { useProfile } from 'lib/profile'
67
import { Button, Input, Separator } from 'ui'
78
import { CATEGORY_OPTIONS } from './Support.constants'
89

910
interface SuccessProps {
1011
sentCategory?: string
1112
selectedProject?: string
12-
projects?: any[]
1313
}
1414

15-
const Success = ({
16-
sentCategory = '',
17-
selectedProject = 'no-project',
18-
projects = [],
19-
}: SuccessProps) => {
15+
export const Success = ({ sentCategory = '', selectedProject = 'no-project' }: SuccessProps) => {
2016
const { profile } = useProfile()
2117
const respondToEmail = profile?.primary_email ?? 'your email'
2218

23-
const project = projects.find((p) => p.ref === selectedProject)
19+
const { data: project } = useProjectDetailQuery(
20+
{ ref: selectedProject },
21+
{ enabled: selectedProject !== 'no-project' }
22+
)
2423
const projectName = project ? project.name : 'No specific project'
2524

2625
const categoriesToShowAdditionalResources = ['Problem', 'Unresponsive', 'Performance']
@@ -98,5 +97,3 @@ const Success = ({
9897
</div>
9998
)
10099
}
101-
102-
export default Success

apps/studio/components/interfaces/Support/SupportFormV2.tsx

Lines changed: 113 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { zodResolver } from '@hookform/resolvers/zod'
22
import * as Sentry from '@sentry/nextjs'
3+
import { AnimatePresence, motion } from 'framer-motion'
34
import {
45
Book,
56
Check,
@@ -13,22 +14,24 @@ import {
1314
X,
1415
} from 'lucide-react'
1516
import Link from 'next/link'
17+
import { useRouter } from 'next/router'
1618
import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react'
1719
import { SubmitHandler, useForm } from 'react-hook-form'
1820
import { toast } from 'sonner'
1921
import * as z from 'zod'
2022

2123
import { useDocsSearch, useParams, type DocsSearchResult as Page } from 'common'
2224
import { CLIENT_LIBRARIES } from 'common/constants'
25+
import CopyButton from 'components/ui/CopyButton'
2326
import { OrganizationProjectSelector } from 'components/ui/OrganizationProjectSelector'
2427
import { getProjectAuthConfig } from 'data/auth/auth-config-query'
2528
import { useSendSupportTicketMutation } from 'data/feedback/support-ticket-send'
2629
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
27-
import { useProjectsQuery } from 'data/projects/projects-query'
30+
import { getProjectDetail } from 'data/projects/project-detail-query'
2831
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
2932
import { detectBrowser } from 'lib/helpers'
3033
import { useProfile } from 'lib/profile'
31-
import { useRouter } from 'next/router'
34+
import { useQueryState } from 'nuqs'
3235
import {
3336
Badge,
3437
Button,
@@ -72,6 +75,41 @@ const MAX_ATTACHMENTS = 5
7275
const INCLUDE_DISCUSSIONS = ['Problem', 'Database_unresponsive']
7376
const CONTAINER_CLASSES = 'px-6'
7477

78+
const FormSchema = z
79+
.object({
80+
organizationSlug: z.string().min(1, 'Please select an organization'),
81+
projectRef: z.string().min(1, 'Please select a project'),
82+
category: z.string().min(1, 'Please select an issue type'),
83+
severity: z.string(),
84+
library: z.string(),
85+
subject: z.string().min(1, 'Please add a subject heading'),
86+
message: z.string().min(1, "Please add a message about the issue that you're facing"),
87+
affectedServices: z.string(),
88+
allowSupportAccess: z.boolean(),
89+
})
90+
.refine(
91+
(data) => {
92+
return !(data.category === 'Problem' && data.library === '')
93+
},
94+
{
95+
message: "Please select the library that you're facing issues with",
96+
path: ['library'],
97+
}
98+
)
99+
100+
const defaultValues = {
101+
organizationSlug: '',
102+
// [Joshen TODO] We should refactor this to accept a null value instead of a magic string
103+
projectRef: 'no-project',
104+
category: '',
105+
severity: 'Low',
106+
library: '',
107+
subject: '',
108+
message: '',
109+
affectedServices: '',
110+
allowSupportAccess: true,
111+
}
112+
75113
interface SupportFormV2Props {
76114
onProjectSelected: (value: string) => void
77115
onOrganizationSelected: (value: string) => void
@@ -86,9 +124,12 @@ export const SupportFormV2 = ({
86124
setSentCategory,
87125
}: SupportFormV2Props) => {
88126
const { profile } = useProfile()
127+
const [highlightRef, setHighlightRef] = useQueryState('highlight', { defaultValue: '' })
128+
129+
// [Joshen] Ideally refactor all these to use nuqs
89130
const {
90-
projectRef: ref,
91-
slug,
131+
projectRef: urlRef,
132+
slug: urlSlug,
92133
category: urlCategory,
93134
subject: urlSubject,
94135
message: urlMessage,
@@ -103,40 +144,6 @@ export const SupportFormV2 = ({
103144
const [uploadedFiles, setUploadedFiles] = useState<File[]>([])
104145
const [uploadedDataUrls, setUploadedDataUrls] = useState<string[]>([])
105146

106-
const FormSchema = z
107-
.object({
108-
organizationSlug: z.string().min(1, 'Please select an organization'),
109-
projectRef: z.string().min(1, 'Please select a project'),
110-
category: z.string().min(1, 'Please select an issue type'),
111-
severity: z.string(),
112-
library: z.string(),
113-
subject: z.string().min(1, 'Please add a subject heading'),
114-
message: z.string().min(1, "Please add a message about the issue that you're facing"),
115-
affectedServices: z.string(),
116-
allowSupportAccess: z.boolean(),
117-
})
118-
.refine(
119-
(data) => {
120-
return !(data.category === 'Problem' && data.library === '')
121-
},
122-
{
123-
message: "Please select the library that you're facing issues with",
124-
path: ['library'],
125-
}
126-
)
127-
128-
const defaultValues = {
129-
organizationSlug: '',
130-
projectRef: 'no-project',
131-
category: '',
132-
severity: 'Low',
133-
library: '',
134-
subject: '',
135-
message: '',
136-
affectedServices: '',
137-
allowSupportAccess: true,
138-
}
139-
140147
const form = useForm<z.infer<typeof FormSchema>>({
141148
mode: 'onBlur',
142149
reValidateMode: 'onBlur',
@@ -158,8 +165,6 @@ export const SupportFormV2 = ({
158165
() => organizations?.find((org) => org.slug === organizationSlug),
159166
[organizationSlug, organizations]
160167
)
161-
const { data, isSuccess: isSuccessProjects } = useProjectsQuery()
162-
const allProjects = data?.projects ?? []
163168

164169
const { mutate: sendEvent } = useSendEventMutation()
165170

@@ -282,28 +287,32 @@ export const SupportFormV2 = ({
282287

283288
useEffect(() => {
284289
// For prefilling form fields via URL, project ref will taking higher precedence than org slug
285-
if (isSuccessOrganizations && isSuccessProjects) {
286-
if (organizations.length === 0) {
287-
form.setValue('organizationSlug', 'no-org')
288-
} else if (ref) {
289-
const selectedProject = allProjects.find((p) => p.ref === ref)
290-
if (selectedProject !== undefined) {
291-
form.setValue('organizationSlug', selectedProject.organization_slug)
292-
form.setValue('projectRef', selectedProject.ref)
293-
}
294-
} else if (slug) {
295-
if (organizations.some((it) => it.slug === slug)) {
296-
form.setValue('organizationSlug', slug)
297-
}
298-
} else if (ref === undefined && slug === undefined) {
299-
const firstOrganization = organizations?.[0]
300-
if (firstOrganization !== undefined) {
301-
form.setValue('organizationSlug', firstOrganization.slug)
290+
const prefillForm = async () => {
291+
if (isSuccessOrganizations) {
292+
if (organizations.length === 0) {
293+
form.setValue('organizationSlug', 'no-org')
294+
} else if (urlRef) {
295+
// Check validity of project via project details
296+
const selectedProject = await getProjectDetail({ ref: urlRef })
297+
if (!!selectedProject) {
298+
const org = organizations.find((x) => x.id === selectedProject.organization_id)
299+
if (!!org) form.setValue('organizationSlug', org.slug)
300+
form.setValue('projectRef', selectedProject.ref)
301+
}
302+
} else if (urlSlug) {
303+
if (organizations.some((it) => it.slug === urlSlug)) {
304+
form.setValue('organizationSlug', urlSlug)
305+
}
306+
} else if (!urlRef && !urlSlug) {
307+
const firstOrganization = organizations?.[0]
308+
if (!!firstOrganization) {
309+
form.setValue('organizationSlug', firstOrganization.slug)
310+
}
302311
}
303312
}
304313
}
305-
// eslint-disable-next-line react-hooks/exhaustive-deps
306-
}, [ref, slug, isSuccessOrganizations, isSuccessProjects])
314+
prefillForm()
315+
}, [urlRef, urlSlug, isSuccessOrganizations])
307316

308317
useEffect(() => {
309318
if (urlCategory) {
@@ -396,19 +405,21 @@ export const SupportFormV2 = ({
396405
)}
397406
/>
398407

399-
<div className={cn(CONTAINER_CLASSES, 'flex flex-col gap-y-2')}>
408+
<div id="projectRef-field" className={cn(CONTAINER_CLASSES, 'flex flex-col gap-y-2')}>
400409
<FormField_Shadcn_
401410
name="projectRef"
402411
control={form.control}
403412
render={({ field }) => (
404-
<FormItemLayout layout="vertical" label="Which project is affected?">
413+
<FormItemLayout hideMessage layout="vertical" label="Which project is affected?">
405414
<FormControl_Shadcn_>
406415
<OrganizationProjectSelector
407416
sameWidthAsTrigger
408417
checkPosition="left"
409418
slug={organizationSlug}
410419
selectedRef={field.value}
411-
onInitialLoad={(projects) => field.onChange(projects[0]?.ref ?? 'no-project')}
420+
onInitialLoad={(projects) => {
421+
if (!urlRef) field.onChange(projects[0]?.ref ?? 'no-project')
422+
}}
412423
onSelect={(project) => field.onChange(project.ref)}
413424
renderTrigger={({ isLoading, project }) => (
414425
<Button
@@ -450,6 +461,46 @@ export const SupportFormV2 = ({
450461
)}
451462
/>
452463

464+
<AnimatePresence>
465+
{projectRef !== 'no-project' && (
466+
<motion.div
467+
initial={{ opacity: 0, height: 0 }}
468+
animate={{ opacity: 1, height: 'auto' }}
469+
exit={{ opacity: 0, height: 0 }}
470+
transition={{ duration: 0.3 }}
471+
className="flex items-center gap-x-1"
472+
>
473+
<p
474+
className={cn(
475+
'text-sm prose transition',
476+
highlightRef ? 'text-foreground' : 'text-foreground-lighter'
477+
)}
478+
>
479+
Project ID:{' '}
480+
<code
481+
className={cn(
482+
'transition',
483+
highlightRef
484+
? 'text-brand font-medium border-brand-500 animate-pulse'
485+
: 'text-foreground-light'
486+
)}
487+
>
488+
{projectRef}
489+
</code>
490+
</p>
491+
<CopyButton
492+
iconOnly
493+
type="text"
494+
text={projectRef}
495+
onClick={() => {
496+
toast.success('Copied to clipboard')
497+
setHighlightRef(null)
498+
}}
499+
/>
500+
</motion.div>
501+
)}
502+
</AnimatePresence>
503+
453504
{organizationSlug &&
454505
subscriptionPlanId !== 'enterprise' &&
455506
category !== 'Login_issues' && (

0 commit comments

Comments
 (0)