mirror of
https://github.com/supabase/supabase.git
synced 2026-07-06 09:44:25 +08:00
* refactor: refactor support form Refactor support form to make it easier to maintain: - Split up large components into smaller components and hooks - Lift state up so we don't have to do complex child/parent state-syncing via useEffect - Use nuqs parsing for consistent serialization/deserialization of support form prefilled fields * test: support form Add comprehensive tests for support form * fix(support form): project and org empty state * Nit clean up * More clean up * cleannnn * fix(support form): allow case-insensitive category in url * clean(support form tests): remove unused param * fix(support form): incorrect logic for sending affected services in payload * clean(support form): use NO_ORG_MARKER and NO_PROJECT_MARKER instead of strings * fix(support form): don't show upgrade cta if already on enterprise --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
182 lines
5.3 KiB
TypeScript
182 lines
5.3 KiB
TypeScript
import { Book, Github, Hash, MessageSquare } from 'lucide-react'
|
|
import {
|
|
createLoader,
|
|
createParser,
|
|
createSerializer,
|
|
type inferParserType,
|
|
parseAsString,
|
|
parseAsStringLiteral,
|
|
type UseQueryStatesKeysMap,
|
|
} from 'nuqs'
|
|
// End of third-party imports
|
|
|
|
import {
|
|
type DocsSearchResult as Page,
|
|
type DocsSearchResultSection as PageSection,
|
|
DocsSearchResultType as PageType,
|
|
} from 'common'
|
|
import { getProjectDetail } from 'data/projects/project-detail-query'
|
|
import { DOCS_URL } from 'lib/constants'
|
|
import type { Organization } from 'types'
|
|
import { CATEGORY_OPTIONS } from './Support.constants'
|
|
|
|
export const NO_PROJECT_MARKER = 'no-project'
|
|
export const NO_ORG_MARKER = 'no-org'
|
|
|
|
export const formatMessage = (
|
|
message: string,
|
|
attachments: string[],
|
|
error: string | null | undefined
|
|
) => {
|
|
const errorString = error != null ? `\nError: ${error}` : ''
|
|
if (attachments.length > 0) {
|
|
const attachmentsImg = attachments.map((url) => `\n${url}`)
|
|
return `${message}\n${attachmentsImg.join('')}${errorString}`
|
|
} else {
|
|
return `${message}${errorString}`
|
|
}
|
|
}
|
|
|
|
export function getPageIcon(page: Page) {
|
|
switch (page.type) {
|
|
case PageType.Markdown:
|
|
case PageType.Reference:
|
|
case PageType.Integration:
|
|
return <Book strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
|
|
case PageType.GithubDiscussion:
|
|
return <Github strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
|
|
default:
|
|
throw new Error(`Unknown page type '${page.type}'`)
|
|
}
|
|
}
|
|
|
|
export function getPageSectionIcon(page: Page) {
|
|
switch (page.type) {
|
|
case PageType.Markdown:
|
|
case PageType.Reference:
|
|
case PageType.Integration:
|
|
return <Hash strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
|
|
case PageType.GithubDiscussion:
|
|
return <MessageSquare strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
|
|
default:
|
|
throw new Error(`Unknown page type '${page.type}'`)
|
|
}
|
|
}
|
|
|
|
export function generateLink(pageType: PageType, link: string): string {
|
|
switch (pageType) {
|
|
case PageType.Markdown:
|
|
case PageType.Reference:
|
|
return `${DOCS_URL}${link}`
|
|
case PageType.Integration:
|
|
return `https://supabase.com${link}`
|
|
case PageType.GithubDiscussion:
|
|
return link
|
|
default:
|
|
throw new Error(`Unknown page type '${pageType}'`)
|
|
}
|
|
}
|
|
|
|
export function formatSectionUrl(page: Page, section: PageSection): string {
|
|
switch (page.type) {
|
|
case PageType.Markdown:
|
|
case PageType.GithubDiscussion:
|
|
return `${generateLink(page.type, page.path)}#${section.slug ?? ''}`
|
|
case PageType.Reference:
|
|
return `${generateLink(page.type, page.path)}/${section.slug ?? ''}`
|
|
case PageType.Integration:
|
|
return generateLink(page.type, page.path) // Assuming no section slug for Integration pages
|
|
default:
|
|
throw new Error(`Unknown page type '${page.type}'`)
|
|
}
|
|
}
|
|
|
|
export function getOrgSubscriptionPlan(orgs: Organization[] | undefined, orgSlug: string | null) {
|
|
if (!orgs || !orgSlug) return undefined
|
|
|
|
const selectedOrg = orgs?.find((org) => org.slug === orgSlug)
|
|
const subscriptionPlanId = selectedOrg?.plan.id
|
|
return subscriptionPlanId
|
|
}
|
|
|
|
const categoryOptionsLower = CATEGORY_OPTIONS.map((option) => option.value.toLowerCase())
|
|
const parseAsCategoryOption = createParser({
|
|
parse(queryValue) {
|
|
const lowerValue = queryValue.toLowerCase()
|
|
const matchingIndex = categoryOptionsLower.indexOf(lowerValue)
|
|
return matchingIndex !== -1 ? CATEGORY_OPTIONS[matchingIndex].value : null
|
|
},
|
|
serialize(value) {
|
|
return value ?? null
|
|
},
|
|
})
|
|
|
|
const supportFormUrlState = {
|
|
projectRef: parseAsString.withDefault(NO_PROJECT_MARKER),
|
|
orgSlug: parseAsString.withDefault(NO_ORG_MARKER),
|
|
category: parseAsCategoryOption,
|
|
subject: parseAsString.withDefault(''),
|
|
message: parseAsString.withDefault(''),
|
|
error: parseAsString,
|
|
/** Sentry event ID */
|
|
sid: parseAsString,
|
|
} satisfies UseQueryStatesKeysMap
|
|
export type SupportFormUrlKeys = inferParserType<typeof supportFormUrlState>
|
|
|
|
export const loadSupportFormInitialParams = createLoader(supportFormUrlState)
|
|
|
|
const serializeSupportFormInitialParams = createSerializer(supportFormUrlState)
|
|
|
|
export function createSupportFormUrl(initialParams: SupportFormUrlKeys) {
|
|
const serializedParams = serializeSupportFormInitialParams(initialParams)
|
|
return `/support/new${serializedParams ?? ''}`
|
|
}
|
|
|
|
/**
|
|
* Determines which organization to select based on combination of:
|
|
* - Selected project (if any)
|
|
* - URL param (if any)
|
|
* - Fallback
|
|
*/
|
|
export async function selectInitalOrgAndProject({
|
|
projectRef,
|
|
orgSlug,
|
|
orgs,
|
|
}: {
|
|
projectRef: string | null
|
|
orgSlug: string | null
|
|
orgs: Organization[]
|
|
}): Promise<{ projectRef: string | null; orgSlug: string | null }> {
|
|
if (projectRef) {
|
|
try {
|
|
const projectDetails = await getProjectDetail({ ref: projectRef })
|
|
if (projectDetails?.organization_id) {
|
|
const org = orgs.find((o) => o.id === projectDetails.organization_id)
|
|
if (org?.slug) {
|
|
return {
|
|
projectRef,
|
|
orgSlug: org.slug,
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
// Can safely ignore, consider provided project ref invalid
|
|
}
|
|
}
|
|
|
|
if (orgSlug) {
|
|
const org = orgs.find((o) => o.slug === orgSlug)
|
|
if (org?.slug) {
|
|
return {
|
|
projectRef: null,
|
|
orgSlug: org.slug,
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
projectRef: null,
|
|
orgSlug: orgs[0]?.slug ?? null,
|
|
}
|
|
}
|