diff --git a/apps/studio/components/interfaces/Reports/ReportFilterBar.tsx b/apps/studio/components/interfaces/Reports/ReportFilterBar.tsx index d8696526c3243..2f411de9dc9ac 100644 --- a/apps/studio/components/interfaces/Reports/ReportFilterBar.tsx +++ b/apps/studio/components/interfaces/Reports/ReportFilterBar.tsx @@ -23,6 +23,7 @@ import { DatePickerValue, LogsDatePicker } from '../Settings/Logs/Logs.DatePicke import { REPORTS_DATEPICKER_HELPERS } from './Reports.constants' import type { ReportFilterItem } from './Reports.types' import { Popover, PopoverContent, PopoverTrigger } from '@ui/components/shadcn/ui/popover' +import { Network } from 'lucide-react' interface ReportFilterBarProps { filters: ReportFilterItem[] @@ -46,8 +47,7 @@ const PRODUCT_FILTERS = [ key: 'rest', filterKey: 'request.path', filterValue: '/rest', - label: 'REST', - description: 'Requests made to PostgREST', + label: 'Data API (PostgREST)', icon: Database, }, { @@ -55,7 +55,6 @@ const PRODUCT_FILTERS = [ filterKey: 'request.path', filterValue: '/auth', label: 'Auth', - description: 'Auth and authorization requests', icon: Auth, }, { @@ -63,7 +62,6 @@ const PRODUCT_FILTERS = [ filterKey: 'request.path', filterValue: '/storage', label: 'Storage', - description: 'Storage asset requests', icon: Storage, }, { @@ -71,15 +69,13 @@ const PRODUCT_FILTERS = [ filterKey: 'request.path', filterValue: '/realtime', label: 'Realtime', - description: 'Realtime connection requests', icon: Realtime, }, { key: 'graphql', filterKey: 'request.path', filterValue: '/graphql', - label: 'GraphQL', - description: 'Requests made to pg_graphql', + label: 'GraphQL (pg_graphql)', icon: null, }, ] @@ -210,7 +206,8 @@ const ReportFilterBar = ({ handleProductFilterChange(null)}> -

All Requests

+ + All Requests
{PRODUCT_FILTERS.map((productFilter) => { @@ -226,13 +223,13 @@ const ReportFilterBar = ({ {productFilter.key === 'graphql' ? ( code.replace(/svg/, 'svg class="m-auto text-color-inherit"') } /> ) : Icon !== null ? ( - + ) : null}

{productFilter.label}

-

- {productFilter.description} -

) diff --git a/apps/studio/components/interfaces/SignIn/ForgotPasswordWizard.tsx b/apps/studio/components/interfaces/SignIn/ForgotPasswordWizard.tsx index 11f39cba3af71..2cc021b1ff414 100644 --- a/apps/studio/components/interfaces/SignIn/ForgotPasswordWizard.tsx +++ b/apps/studio/components/interfaces/SignIn/ForgotPasswordWizard.tsx @@ -10,6 +10,7 @@ import { useResetPasswordMutation } from 'data/misc/reset-password-mutation' import { BASE_PATH } from 'lib/constants' import { auth } from 'lib/gotrue' import { Button, Form_Shadcn_, FormControl_Shadcn_, FormField_Shadcn_, Input_Shadcn_ } from 'ui' +import { Admonition } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' const forgotPasswordSchema = z.object({ @@ -78,6 +79,11 @@ const ConfirmResetCodeForm = ({ email }: { email: string }) => { className="flex flex-col pt-4 space-y-4" onSubmit={codeForm.handleSubmit(onCodeEntered)} > + { )}
  • - Force sign out and clear cookies + + Force sign out and clear cookies +
  • {bucket.name} @@ -106,7 +107,7 @@ export const BucketRow = ({ bucket, projectRef = '', isSelected = false }: Bucke ) : ( -

    +
    )} diff --git a/apps/studio/components/interfaces/Support/Success.tsx b/apps/studio/components/interfaces/Support/Success.tsx index ff8f5037a0e98..4d428c6a376e6 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 2de8d272e0b47..b94e23d8e8a7a 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.