From 607dafefbde667a95f2d8d63a26423c228497043 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 26 Sep 2025 15:34:10 +0800 Subject: [PATCH] Remove useProjectsQuery from support form (#39026) * Remove useProjectsQuery from support form * Add comment for refactor --- .../components/interfaces/Support/Success.tsx | 15 +- .../interfaces/Support/SupportFormV2.tsx | 175 +++++++++++------- apps/studio/pages/support/new.tsx | 71 +++---- 3 files changed, 140 insertions(+), 121 deletions(-) diff --git a/apps/studio/components/interfaces/Support/Success.tsx b/apps/studio/components/interfaces/Support/Success.tsx index ff8f5037a0..4d428c6a37 100644 --- a/apps/studio/components/interfaces/Support/Success.tsx +++ b/apps/studio/components/interfaces/Support/Success.tsx @@ -2,6 +2,7 @@ import { Check, ExternalLink, Mail, Search } from 'lucide-react' import Link from 'next/link' import { useState } from 'react' +import { useProjectDetailQuery } from 'data/projects/project-detail-query' import { useProfile } from 'lib/profile' import { Button, Input, Separator } from 'ui' import { CATEGORY_OPTIONS } from './Support.constants' @@ -9,18 +10,16 @@ import { CATEGORY_OPTIONS } from './Support.constants' interface SuccessProps { sentCategory?: string selectedProject?: string - projects?: any[] } -const Success = ({ - sentCategory = '', - selectedProject = 'no-project', - projects = [], -}: SuccessProps) => { +export const Success = ({ sentCategory = '', selectedProject = 'no-project' }: SuccessProps) => { const { profile } = useProfile() const respondToEmail = profile?.primary_email ?? 'your email' - const project = projects.find((p) => p.ref === selectedProject) + const { data: project } = useProjectDetailQuery( + { ref: selectedProject }, + { enabled: selectedProject !== 'no-project' } + ) const projectName = project ? project.name : 'No specific project' const categoriesToShowAdditionalResources = ['Problem', 'Unresponsive', 'Performance'] @@ -98,5 +97,3 @@ const Success = ({ ) } - -export default Success diff --git a/apps/studio/components/interfaces/Support/SupportFormV2.tsx b/apps/studio/components/interfaces/Support/SupportFormV2.tsx index 2de8d272e0..b94e23d8e8 100644 --- a/apps/studio/components/interfaces/Support/SupportFormV2.tsx +++ b/apps/studio/components/interfaces/Support/SupportFormV2.tsx @@ -1,5 +1,6 @@ import { zodResolver } from '@hookform/resolvers/zod' import * as Sentry from '@sentry/nextjs' +import { AnimatePresence, motion } from 'framer-motion' import { Book, Check, @@ -13,6 +14,7 @@ import { X, } from 'lucide-react' import Link from 'next/link' +import { useRouter } from 'next/router' import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' @@ -20,15 +22,16 @@ import * as z from 'zod' import { useDocsSearch, useParams, type DocsSearchResult as Page } from 'common' import { CLIENT_LIBRARIES } from 'common/constants' +import CopyButton from 'components/ui/CopyButton' import { OrganizationProjectSelector } from 'components/ui/OrganizationProjectSelector' import { getProjectAuthConfig } from 'data/auth/auth-config-query' import { useSendSupportTicketMutation } from 'data/feedback/support-ticket-send' import { useOrganizationsQuery } from 'data/organizations/organizations-query' -import { useProjectsQuery } from 'data/projects/projects-query' +import { getProjectDetail } from 'data/projects/project-detail-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { detectBrowser } from 'lib/helpers' import { useProfile } from 'lib/profile' -import { useRouter } from 'next/router' +import { useQueryState } from 'nuqs' import { Badge, Button, @@ -72,6 +75,41 @@ const MAX_ATTACHMENTS = 5 const INCLUDE_DISCUSSIONS = ['Problem', 'Database_unresponsive'] const CONTAINER_CLASSES = 'px-6' +const FormSchema = z + .object({ + organizationSlug: z.string().min(1, 'Please select an organization'), + projectRef: z.string().min(1, 'Please select a project'), + category: z.string().min(1, 'Please select an issue type'), + severity: z.string(), + library: z.string(), + subject: z.string().min(1, 'Please add a subject heading'), + message: z.string().min(1, "Please add a message about the issue that you're facing"), + affectedServices: z.string(), + allowSupportAccess: z.boolean(), + }) + .refine( + (data) => { + return !(data.category === 'Problem' && data.library === '') + }, + { + message: "Please select the library that you're facing issues with", + path: ['library'], + } + ) + +const defaultValues = { + organizationSlug: '', + // [Joshen TODO] We should refactor this to accept a null value instead of a magic string + projectRef: 'no-project', + category: '', + severity: 'Low', + library: '', + subject: '', + message: '', + affectedServices: '', + allowSupportAccess: true, +} + interface SupportFormV2Props { onProjectSelected: (value: string) => void onOrganizationSelected: (value: string) => void @@ -86,9 +124,12 @@ export const SupportFormV2 = ({ setSentCategory, }: SupportFormV2Props) => { const { profile } = useProfile() + const [highlightRef, setHighlightRef] = useQueryState('highlight', { defaultValue: '' }) + + // [Joshen] Ideally refactor all these to use nuqs const { - projectRef: ref, - slug, + projectRef: urlRef, + slug: urlSlug, category: urlCategory, subject: urlSubject, message: urlMessage, @@ -103,40 +144,6 @@ export const SupportFormV2 = ({ const [uploadedFiles, setUploadedFiles] = useState([]) const [uploadedDataUrls, setUploadedDataUrls] = useState([]) - const FormSchema = z - .object({ - organizationSlug: z.string().min(1, 'Please select an organization'), - projectRef: z.string().min(1, 'Please select a project'), - category: z.string().min(1, 'Please select an issue type'), - severity: z.string(), - library: z.string(), - subject: z.string().min(1, 'Please add a subject heading'), - message: z.string().min(1, "Please add a message about the issue that you're facing"), - affectedServices: z.string(), - allowSupportAccess: z.boolean(), - }) - .refine( - (data) => { - return !(data.category === 'Problem' && data.library === '') - }, - { - message: "Please select the library that you're facing issues with", - path: ['library'], - } - ) - - const defaultValues = { - organizationSlug: '', - projectRef: 'no-project', - category: '', - severity: 'Low', - library: '', - subject: '', - message: '', - affectedServices: '', - allowSupportAccess: true, - } - const form = useForm>({ mode: 'onBlur', reValidateMode: 'onBlur', @@ -158,8 +165,6 @@ export const SupportFormV2 = ({ () => organizations?.find((org) => org.slug === organizationSlug), [organizationSlug, organizations] ) - const { data, isSuccess: isSuccessProjects } = useProjectsQuery() - const allProjects = data?.projects ?? [] const { mutate: sendEvent } = useSendEventMutation() @@ -282,28 +287,32 @@ export const SupportFormV2 = ({ useEffect(() => { // For prefilling form fields via URL, project ref will taking higher precedence than org slug - if (isSuccessOrganizations && isSuccessProjects) { - if (organizations.length === 0) { - form.setValue('organizationSlug', 'no-org') - } else if (ref) { - const selectedProject = allProjects.find((p) => p.ref === ref) - if (selectedProject !== undefined) { - form.setValue('organizationSlug', selectedProject.organization_slug) - form.setValue('projectRef', selectedProject.ref) - } - } else if (slug) { - if (organizations.some((it) => it.slug === slug)) { - form.setValue('organizationSlug', slug) - } - } else if (ref === undefined && slug === undefined) { - const firstOrganization = organizations?.[0] - if (firstOrganization !== undefined) { - form.setValue('organizationSlug', firstOrganization.slug) + const prefillForm = async () => { + if (isSuccessOrganizations) { + if (organizations.length === 0) { + form.setValue('organizationSlug', 'no-org') + } else if (urlRef) { + // Check validity of project via project details + const selectedProject = await getProjectDetail({ ref: urlRef }) + if (!!selectedProject) { + const org = organizations.find((x) => x.id === selectedProject.organization_id) + if (!!org) form.setValue('organizationSlug', org.slug) + form.setValue('projectRef', selectedProject.ref) + } + } else if (urlSlug) { + if (organizations.some((it) => it.slug === urlSlug)) { + form.setValue('organizationSlug', urlSlug) + } + } else if (!urlRef && !urlSlug) { + const firstOrganization = organizations?.[0] + if (!!firstOrganization) { + form.setValue('organizationSlug', firstOrganization.slug) + } } } } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ref, slug, isSuccessOrganizations, isSuccessProjects]) + prefillForm() + }, [urlRef, urlSlug, isSuccessOrganizations]) useEffect(() => { if (urlCategory) { @@ -396,19 +405,21 @@ export const SupportFormV2 = ({ )} /> -
+
( - + field.onChange(projects[0]?.ref ?? 'no-project')} + onInitialLoad={(projects) => { + if (!urlRef) field.onChange(projects[0]?.ref ?? 'no-project') + }} onSelect={(project) => field.onChange(project.ref)} renderTrigger={({ isLoading, project }) => ( - - ))} - - - {' '} + { + const el = document.getElementById('projectRef-field') + el?.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' }) + setHighlightRef('true') + }} + > + include your project ID + {' '} and as much information as possible.