mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 19:35:06 +03:00
## What kind of change does this PR introduce? Refactor. Resolves FE-3216. ## What is the current behavior? The AWS Marketplace create-organisation dialog owns its own copy of the organisation name, type, and company-size form fields. It’s duplicative and has drifted from the normal `/new` organisation form, making the AWS flow harder to keep aligned. This is stacked on #46058. ## What is the new behavior? - Extracts the shared organisation details schema, defaults, option constants, and fields from the normal `NewOrgForm` - Reuses those shared fields in both the full organisation creation form and the AWS Marketplace create-and-link dialog - Keeps the AWS Marketplace flow anchored in the onboarding interstitial rather than routing through `/new` - Keeps the AWS-specific buyer ID, AWS-managed organisation endpoint, create-and-link success state, and modal dismissal behaviour | Before | After | | --- | --- | | <img width="1024" height="759" alt="Link AWS Marketplace Supabase-3742FEDF-53BD-4E80-926D-498B2EA94773" src="https://github.com/user-attachments/assets/617ee422-1cf0-4858-801b-a4ee5ee402c9" /> | <img width="1024" height="759" alt="Link AWS Marketplace Supabase-0FEE2292-CB9F-43AA-B131-B6A549890970" src="https://github.com/user-attachments/assets/ff017468-f8ac-469a-bb17-eea07842306f" /> | ## Additional context The shared field extraction is intentionally limited to organisation details. Billing, plan selection, spend cap, Stripe, captcha, and the `/new/[slug]` redirect behaviour stay owned by `NewOrgForm`; AWS Marketplace keeps its separate create-and-link container because AWS owns the billing contract. ## Validation - `pnpm --filter studio exec vitest --run tests/pages/aws-marketplace-onboarding.test.tsx` - `pnpm --filter studio lint:ratchet --rule no-restricted-exports` - `git diff --check` Full Studio typecheck was also run, but it currently fails on existing unrelated repo-wide React/implicit-any errors outside this diff. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved AWS Marketplace organization creation dialog to prevent accidental closure while the creation process is in progress. * **Improvements** * Standardized organization details form handling across different organization creation flows for improved consistency and user experience. * **Tests** * Added comprehensive test coverage for the AWS Marketplace organization creation workflow, including form submission, validation, and state transitions. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46087?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
171 lines
4.6 KiB
TypeScript
171 lines
4.6 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
import type { Control, FieldPath, FieldValues } from 'react-hook-form'
|
|
import {
|
|
FormControl,
|
|
FormField,
|
|
Input,
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from 'ui'
|
|
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
|
import { z } from 'zod'
|
|
|
|
export const ORG_KIND_TYPES = {
|
|
PERSONAL: 'Personal',
|
|
EDUCATIONAL: 'Educational',
|
|
STARTUP: 'Startup',
|
|
AGENCY: 'Agency',
|
|
COMPANY: 'Company',
|
|
UNDISCLOSED: 'N/A',
|
|
}
|
|
|
|
export const ORG_KIND_DEFAULT = 'PERSONAL'
|
|
|
|
export const ORG_SIZE_TYPES = {
|
|
'1': '1 - 10',
|
|
'10': '10 - 49',
|
|
'50': '50 - 99',
|
|
'100': '100 - 299',
|
|
'300': 'More than 300',
|
|
}
|
|
|
|
export const ORG_SIZE_DEFAULT = '1'
|
|
|
|
export type OrgKind = keyof typeof ORG_KIND_TYPES
|
|
export type OrgSize = keyof typeof ORG_SIZE_TYPES
|
|
|
|
const orgKindValues = [
|
|
'PERSONAL',
|
|
'EDUCATIONAL',
|
|
'STARTUP',
|
|
'AGENCY',
|
|
'COMPANY',
|
|
'UNDISCLOSED',
|
|
] as const
|
|
|
|
const orgSizeValues = ['1', '10', '50', '100', '300'] as const
|
|
|
|
export const organizationDetailsSchema = z.object({
|
|
name: z.string().trim().min(1, 'Organization name is required'),
|
|
kind: z
|
|
.string()
|
|
.transform((val) => val.toUpperCase())
|
|
.pipe(z.enum(orgKindValues)),
|
|
size: z.enum(orgSizeValues),
|
|
})
|
|
|
|
export type OrganizationDetailsFormValues = z.infer<typeof organizationDetailsSchema>
|
|
|
|
interface OrganizationDetailsFieldsProps<TFieldValues extends FieldValues> {
|
|
control: Control<TFieldValues>
|
|
kind: string
|
|
nameField?: FieldPath<TFieldValues>
|
|
kindField?: FieldPath<TFieldValues>
|
|
sizeField?: FieldPath<TFieldValues>
|
|
renderFieldWrapper?: (children: ReactNode, field: 'name' | 'kind' | 'size') => ReactNode
|
|
}
|
|
|
|
export const OrganizationDetailsFields = <TFieldValues extends FieldValues>({
|
|
control,
|
|
kind,
|
|
nameField = 'name' as FieldPath<TFieldValues>,
|
|
kindField = 'kind' as FieldPath<TFieldValues>,
|
|
sizeField = 'size' as FieldPath<TFieldValues>,
|
|
renderFieldWrapper = (children) => children,
|
|
}: OrganizationDetailsFieldsProps<TFieldValues>) => (
|
|
<>
|
|
{renderFieldWrapper(
|
|
<FormField
|
|
control={control}
|
|
name={nameField}
|
|
render={({ field }) => (
|
|
<FormItemLayout
|
|
label="Name"
|
|
layout="horizontal"
|
|
description="What's the name of your company or team? You can change this later."
|
|
>
|
|
<FormControl>
|
|
<Input
|
|
autoFocus
|
|
type="text"
|
|
placeholder="Organization name"
|
|
data-1p-ignore
|
|
data-lpignore="true"
|
|
data-form-type="other"
|
|
data-bwignore
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>,
|
|
'name'
|
|
)}
|
|
{renderFieldWrapper(
|
|
<FormField
|
|
control={control}
|
|
name={kindField}
|
|
render={({ field }) => (
|
|
<FormItemLayout
|
|
label="Type"
|
|
layout="horizontal"
|
|
description="What best describes your organization?"
|
|
>
|
|
<FormControl>
|
|
<Select value={field.value} onValueChange={field.onChange}>
|
|
<SelectTrigger className="w-full">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
|
|
<SelectContent>
|
|
{Object.entries(ORG_KIND_TYPES).map(([key, label]) => (
|
|
<SelectItem key={key} value={key}>
|
|
{label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>,
|
|
'kind'
|
|
)}
|
|
|
|
{kind === 'COMPANY' &&
|
|
renderFieldWrapper(
|
|
<FormField
|
|
control={control}
|
|
name={sizeField}
|
|
render={({ field }) => (
|
|
<FormItemLayout
|
|
label="Company size"
|
|
layout="horizontal"
|
|
description="How many people are in your company?"
|
|
>
|
|
<FormControl>
|
|
<Select value={field.value} onValueChange={field.onChange}>
|
|
<SelectTrigger className="w-full">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
|
|
<SelectContent>
|
|
{Object.entries(ORG_SIZE_TYPES).map(([key, label]) => (
|
|
<SelectItem key={key} value={key}>
|
|
{label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>,
|
|
'size'
|
|
)}
|
|
</>
|
|
)
|