Remove useProjectsQuery from support form (#39026)
* Remove useProjectsQuery from support form * Add comment for refactor
This commit is contained in:
@@ -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 = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Success
|
||||
|
||||
@@ -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<File[]>([])
|
||||
const [uploadedDataUrls, setUploadedDataUrls] = useState<string[]>([])
|
||||
|
||||
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<z.infer<typeof FormSchema>>({
|
||||
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 = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className={cn(CONTAINER_CLASSES, 'flex flex-col gap-y-2')}>
|
||||
<div id="projectRef-field" className={cn(CONTAINER_CLASSES, 'flex flex-col gap-y-2')}>
|
||||
<FormField_Shadcn_
|
||||
name="projectRef"
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItemLayout layout="vertical" label="Which project is affected?">
|
||||
<FormItemLayout hideMessage layout="vertical" label="Which project is affected?">
|
||||
<FormControl_Shadcn_>
|
||||
<OrganizationProjectSelector
|
||||
sameWidthAsTrigger
|
||||
checkPosition="left"
|
||||
slug={organizationSlug}
|
||||
selectedRef={field.value}
|
||||
onInitialLoad={(projects) => 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 }) => (
|
||||
<Button
|
||||
@@ -450,6 +461,46 @@ export const SupportFormV2 = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<AnimatePresence>
|
||||
{projectRef !== 'no-project' && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="flex items-center gap-x-1"
|
||||
>
|
||||
<p
|
||||
className={cn(
|
||||
'text-sm prose transition',
|
||||
highlightRef ? 'text-foreground' : 'text-foreground-lighter'
|
||||
)}
|
||||
>
|
||||
Project ID:{' '}
|
||||
<code
|
||||
className={cn(
|
||||
'transition',
|
||||
highlightRef
|
||||
? 'text-brand font-medium border-brand-500 animate-pulse'
|
||||
: 'text-foreground-light'
|
||||
)}
|
||||
>
|
||||
{projectRef}
|
||||
</code>
|
||||
</p>
|
||||
<CopyButton
|
||||
iconOnly
|
||||
type="text"
|
||||
text={projectRef}
|
||||
onClick={() => {
|
||||
toast.success('Copied to clipboard')
|
||||
setHighlightRef(null)
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{organizationSlug &&
|
||||
subscriptionPlanId !== 'enterprise' &&
|
||||
category !== 'Login_issues' && (
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import { ClipboardIcon, Loader2, Wrench } from 'lucide-react'
|
||||
import { Loader2, Wrench } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import SVG from 'react-inlinesvg'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { AIAssistantOption } from 'components/interfaces/Support/AIAssistantOption'
|
||||
import Success from 'components/interfaces/Support/Success'
|
||||
import { Success } from 'components/interfaces/Support/Success'
|
||||
import { SupportFormV2 } from 'components/interfaces/Support/SupportFormV2'
|
||||
import AppLayout from 'components/layouts/AppLayout/AppLayout'
|
||||
import DefaultLayout from 'components/layouts/DefaultLayout'
|
||||
import CopyButton from 'components/ui/CopyButton'
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
import { InlineLink, InlineLinkClassName } from 'components/ui/InlineLink'
|
||||
import { usePlatformStatusQuery } from 'data/platform/platform-status-query'
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { withAuth } from 'hooks/misc/withAuth'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import { toast } from 'sonner'
|
||||
import { useQueryState } from 'nuqs'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { Button, copyToClipboard, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
import { Button, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
|
||||
const SupportPage: NextPageWithLayout = () => {
|
||||
const [sentCategory, setSentCategory] = useState<string>()
|
||||
@@ -27,7 +28,7 @@ const SupportPage: NextPageWithLayout = () => {
|
||||
const { data, isLoading } = usePlatformStatusQuery()
|
||||
const isHealthy = data?.isHealthy
|
||||
|
||||
const { data: projectsData } = useProjectsQuery()
|
||||
const [_, setHighlightRef] = useQueryState('highlight', { defaultValue: '' })
|
||||
|
||||
return (
|
||||
<div className="relative flex overflow-y-auto overflow-x-hidden">
|
||||
@@ -88,11 +89,7 @@ const SupportPage: NextPageWithLayout = () => {
|
||||
].join(' ')}
|
||||
>
|
||||
{sentCategory !== undefined ? (
|
||||
<Success
|
||||
sentCategory={sentCategory}
|
||||
selectedProject={selectedProject}
|
||||
projects={projectsData?.projects}
|
||||
/>
|
||||
<Success sentCategory={sentCategory} selectedProject={selectedProject} />
|
||||
) : (
|
||||
<SupportFormV2
|
||||
onProjectSelected={setSelectedProject}
|
||||
@@ -106,14 +103,11 @@ const SupportPage: NextPageWithLayout = () => {
|
||||
title="Having trouble submitting the form?"
|
||||
description={
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<p className="flex items-center gap-x-1">
|
||||
<p className="flex items-center gap-x-1 ">
|
||||
Email us directly at{' '}
|
||||
<Link
|
||||
href="mailto:support@supabase.com"
|
||||
className="p-1 font-mono rounded-md text-foreground"
|
||||
>
|
||||
<InlineLink href="mailto:support@supabase.com" className="font-mono">
|
||||
support@supabase.com
|
||||
</Link>
|
||||
</InlineLink>
|
||||
<CopyButton
|
||||
type="text"
|
||||
text="support@supabase.com"
|
||||
@@ -123,39 +117,16 @@ const SupportPage: NextPageWithLayout = () => {
|
||||
</p>
|
||||
<p>
|
||||
Please, make sure to{' '}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="text-foreground underline">include your project ID</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-0">
|
||||
<ul className="p-2">
|
||||
<li className="grid pb-1 grid-cols-2 px-2 text-foreground-lighter">
|
||||
<span>Project name</span>
|
||||
<span>ID</span>
|
||||
</li>
|
||||
{(projectsData?.projects ?? []).map((project) => (
|
||||
<li key={project.id} className="cursor-default">
|
||||
<button
|
||||
onClick={() => {
|
||||
copyToClipboard(project.ref)
|
||||
toast.success('Copied to clipboard')
|
||||
}}
|
||||
className="w-full group py-1.5 px-2 gap-x-1 text-foreground hover:bg-muted grid grid-cols-2 text-left rounded-sm"
|
||||
>
|
||||
<span className="truncate max-w-40">{project.name}</span>
|
||||
<span className="flex w-full gap-x-1 items-center font-mono">
|
||||
{project.ref}
|
||||
<ClipboardIcon
|
||||
size="14"
|
||||
className="text-foreground-lighter opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</TooltipContent>
|
||||
</Tooltip>{' '}
|
||||
<span
|
||||
className={cn(InlineLinkClassName, 'cursor-pointer')}
|
||||
onClick={() => {
|
||||
const el = document.getElementById('projectRef-field')
|
||||
el?.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' })
|
||||
setHighlightRef('true')
|
||||
}}
|
||||
>
|
||||
include your project ID
|
||||
</span>{' '}
|
||||
and as much information as possible.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user