mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
Merge pull request #16563 from supabase/chore/convert-requests-to-RQ-6
Convert independent POST requests to RQ P4
This commit is contained in:
10 files changed
+257
-188
No files matched your search
@@ -9,6 +9,7 @@ interface ProductEmptyStateProps {
|
||||
infoButtonLabel?: string
|
||||
infoButtonUrl?: string
|
||||
onClickCta?: () => void
|
||||
loading?: boolean
|
||||
disabled?: boolean
|
||||
disabledMessage?: string
|
||||
}
|
||||
@@ -21,6 +22,7 @@ const ProductEmptyState = ({
|
||||
infoButtonLabel = '',
|
||||
infoButtonUrl = '',
|
||||
onClickCta = () => {},
|
||||
loading = false,
|
||||
disabled = false,
|
||||
disabledMessage = '',
|
||||
}: PropsWithChildren<ProductEmptyStateProps>) => {
|
||||
@@ -39,7 +41,12 @@ const ProductEmptyState = ({
|
||||
{ctaButtonLabel && onClickCta && (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button type="primary" onClick={onClickCta} disabled={disabled}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onClickCta}
|
||||
loading={loading}
|
||||
disabled={loading || disabled}
|
||||
>
|
||||
{ctaButtonLabel}
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useMutation, UseMutationOptions } from '@tanstack/react-query'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { post } from 'data/fetchers'
|
||||
import { ResponseError } from 'types'
|
||||
|
||||
export type HooksEnableVariables = {
|
||||
ref: string
|
||||
}
|
||||
|
||||
export async function enableDatabaseWebhooks({ ref }: HooksEnableVariables) {
|
||||
const { data, error } = await post('/platform/database/{ref}/hook-enable', {
|
||||
params: { path: { ref } },
|
||||
})
|
||||
if (error) throw error
|
||||
return data
|
||||
}
|
||||
|
||||
type HooksEnableData = Awaited<ReturnType<typeof enableDatabaseWebhooks>>
|
||||
|
||||
export const useHooksEnableMutation = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
...options
|
||||
}: Omit<
|
||||
UseMutationOptions<HooksEnableData, ResponseError, HooksEnableVariables>,
|
||||
'mutationFn'
|
||||
> = {}) => {
|
||||
return useMutation<HooksEnableData, ResponseError, HooksEnableVariables>(
|
||||
(vars) => enableDatabaseWebhooks(vars),
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
if (onError === undefined) {
|
||||
toast.error(`Failed to enable webhooks: ${data.message}`)
|
||||
} else {
|
||||
onError(data, variables, context)
|
||||
}
|
||||
},
|
||||
...options,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { post } from 'data/fetchers'
|
||||
import { ResponseError } from 'types'
|
||||
import { organizationKeys } from './keys'
|
||||
|
||||
export type OrganizationCreateVariables = {
|
||||
name: string
|
||||
kind?: string
|
||||
size?: string
|
||||
}
|
||||
|
||||
export async function createOrganization({ name, kind, size }: OrganizationCreateVariables) {
|
||||
const { data, error } = await post('/platform/organizations', {
|
||||
// @ts-ignore [Joshen] Generated API spec is wrong?
|
||||
body: { name, kind, ...(kind == 'COMPANY' ? { size } : {}) },
|
||||
})
|
||||
|
||||
if (error) throw error
|
||||
return data
|
||||
}
|
||||
|
||||
type OrganizationCreateData = Awaited<ReturnType<typeof createOrganization>>
|
||||
|
||||
export const useOrganizationCreateMutation = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
...options
|
||||
}: Omit<
|
||||
UseMutationOptions<OrganizationCreateData, ResponseError, OrganizationCreateVariables>,
|
||||
'mutationFn'
|
||||
> = {}) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation<OrganizationCreateData, ResponseError, OrganizationCreateVariables>(
|
||||
(vars) => createOrganization(vars),
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
await queryClient.invalidateQueries(organizationKeys.list())
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
if (onError === undefined) {
|
||||
toast.error(`Failed to create organization: ${data.message}`)
|
||||
} else {
|
||||
onError(data, variables, context)
|
||||
}
|
||||
},
|
||||
...options,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -12,7 +12,11 @@ export type ProjectCreateVariables = {
|
||||
dbPass: string
|
||||
dbRegion: string
|
||||
dbSql?: string
|
||||
dbPricingTierId?: string
|
||||
cloudProvider?: string
|
||||
configurationId?: string
|
||||
authSiteUrl?: string
|
||||
customSupabaseRequest?: object
|
||||
}
|
||||
|
||||
export async function createProject({
|
||||
@@ -21,18 +25,25 @@ export async function createProject({
|
||||
dbPass,
|
||||
dbRegion,
|
||||
dbSql,
|
||||
dbPricingTierId = PRICING_TIER_PRODUCT_IDS.FREE,
|
||||
cloudProvider = PROVIDERS.AWS.id,
|
||||
configurationId,
|
||||
authSiteUrl,
|
||||
customSupabaseRequest,
|
||||
}: ProjectCreateVariables) {
|
||||
const response = await post(`${API_URL}/projects`, {
|
||||
cloud_provider: PROVIDERS.AWS.id, // hardcoded for DB instances to be under AWS
|
||||
cloud_provider: cloudProvider,
|
||||
org_id: organizationId,
|
||||
name,
|
||||
db_pass: dbPass,
|
||||
db_region: dbRegion,
|
||||
db_sql: dbSql,
|
||||
db_pricing_tier_id: PRICING_TIER_PRODUCT_IDS.FREE,
|
||||
// auth_site_url: _store.selectedVercelProjectUrl,
|
||||
db_pricing_tier_id: dbPricingTierId,
|
||||
auth_site_url: authSiteUrl,
|
||||
vercel_configuration_id: configurationId,
|
||||
...(customSupabaseRequest !== undefined && {
|
||||
custom_supabase_internal_requests: customSupabaseRequest,
|
||||
}),
|
||||
})
|
||||
if (response.error) throw response.error
|
||||
return response as ProjectBase
|
||||
@@ -59,7 +70,7 @@ export const useProjectCreateMutation = ({
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
if (onError === undefined) {
|
||||
toast.error(`Failed to create project: ${data.message}`)
|
||||
toast.error(`Failed to create new project: ${data.message}`)
|
||||
} else {
|
||||
onError(data, variables, context)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { getAccessToken } from 'lib/gotrue'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { SupaResponse } from 'types/base'
|
||||
import { Session } from '@supabase/gotrue-js'
|
||||
|
||||
export function handleError<T>(e: any, requestId: string): SupaResponse<T> {
|
||||
const message = e?.message ? `An error has occurred: ${e.message}` : 'An error has occurred'
|
||||
|
||||
+29
-30
@@ -4,7 +4,7 @@ import generator from 'generate-password'
|
||||
import { debounce, isUndefined } from 'lodash'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import Link from 'next/link'
|
||||
import Router, { useRouter } from 'next/router'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
Alert,
|
||||
@@ -32,12 +32,14 @@ import PasswordStrengthBar from 'components/ui/PasswordStrengthBar'
|
||||
import { useFreeProjectLimitCheckQuery } from 'data/organizations/free-project-limit-check-query'
|
||||
import { useOrganizationPaymentMethodsQuery } from 'data/organizations/organization-payment-methods-query'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import {
|
||||
ProjectCreateVariables,
|
||||
useProjectCreateMutation,
|
||||
} from 'data/projects/project-create-mutation'
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { useCheckPermissions, useFlag, useStore, withAuth } from 'hooks'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import {
|
||||
API_URL,
|
||||
AWS_REGIONS,
|
||||
CloudProvider,
|
||||
DEFAULT_MINIMUM_PASSWORD_STRENGTH,
|
||||
@@ -70,7 +72,6 @@ const Wizard: NextPageWithLayout = () => {
|
||||
const [dbPass, setDbPass] = useState('')
|
||||
const [dbRegion, setDbRegion] = useState(PROVIDERS[cloudProvider].default_region)
|
||||
const [dbPricingTierKey, setDbPricingTierKey] = useState(PRICING_TIER_DEFAULT_KEY)
|
||||
const [newProjectedLoading, setNewProjectLoading] = useState(false)
|
||||
const [passwordStrengthMessage, setPasswordStrengthMessage] = useState('')
|
||||
const [passwordStrengthWarning, setPasswordStrengthWarning] = useState('')
|
||||
const [passwordStrengthScore, setPasswordStrengthScore] = useState(0)
|
||||
@@ -93,6 +94,12 @@ const Wizard: NextPageWithLayout = () => {
|
||||
enabled: currentOrg?.subscription_id != undefined,
|
||||
})
|
||||
|
||||
const { mutate: createProject, isLoading: isCreatingNewProject } = useProjectCreateMutation({
|
||||
onSuccess: (res) => {
|
||||
router.push(`/project/${res.ref}/building`)
|
||||
},
|
||||
})
|
||||
|
||||
const orgProjectCount = (allProjects || []).filter(
|
||||
(proj) => proj.organization_id === currentOrg?.id
|
||||
).length
|
||||
@@ -186,43 +193,31 @@ const Wizard: NextPageWithLayout = () => {
|
||||
}
|
||||
|
||||
const onClickNext = async () => {
|
||||
setNewProjectLoading(true)
|
||||
if (!currentOrg) return console.error('Unable to retrieve current organization')
|
||||
|
||||
const dbTier = dbPricingTierKey === 'PRO' && !isSpendCapEnabled ? 'PAYG' : dbPricingTierKey
|
||||
|
||||
const data: Record<string, any> = {
|
||||
cloud_provider: cloudProvider,
|
||||
org_id: currentOrg?.id,
|
||||
const data: ProjectCreateVariables = {
|
||||
cloudProvider,
|
||||
organizationId: currentOrg.id,
|
||||
name: projectName.trim(),
|
||||
db_pass: dbPass,
|
||||
db_region: dbRegion,
|
||||
db_pricing_tier_id: (PRICING_TIER_PRODUCT_IDS as any)[dbTier],
|
||||
dbPass: dbPass,
|
||||
dbRegion: dbRegion,
|
||||
dbPricingTierId: (PRICING_TIER_PRODUCT_IDS as any)[dbTier],
|
||||
}
|
||||
if (postgresVersion) {
|
||||
if (!postgresVersion.match(/1[2-9]\..*/)) {
|
||||
setNewProjectLoading(false)
|
||||
ui.setNotification({
|
||||
return ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Invalid Postgres version, should start with a number between 12-19, a dot and additional characters, i.e. 15.2 or 15.2.0-3`,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
data['custom_supabase_internal_requests'] = {
|
||||
data['customSupabaseRequest'] = {
|
||||
ami: { search_tags: { 'tag:postgresVersion': postgresVersion } },
|
||||
}
|
||||
}
|
||||
const response = await post(`${API_URL}/projects`, data)
|
||||
if (response.error) {
|
||||
setNewProjectLoading(false)
|
||||
ui.setNotification({
|
||||
error: response.error,
|
||||
category: 'error',
|
||||
message: `Failed to create new project: ${response.error.message}`,
|
||||
})
|
||||
} else {
|
||||
router.push(`/project/${response.ref}/building`)
|
||||
}
|
||||
|
||||
createProject(data)
|
||||
}
|
||||
|
||||
// [Joshen] Refactor: DB Password could be a common component
|
||||
@@ -263,7 +258,11 @@ const Wizard: NextPageWithLayout = () => {
|
||||
}
|
||||
footer={
|
||||
<div key="panel-footer" className="flex items-center justify-between w-full">
|
||||
<Button type="default" onClick={() => Router.push('/projects')}>
|
||||
<Button
|
||||
type="default"
|
||||
disabled={isCreatingNewProject}
|
||||
onClick={() => router.push('/projects')}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<div className="items-center space-x-3">
|
||||
@@ -272,8 +271,8 @@ const Wizard: NextPageWithLayout = () => {
|
||||
)}
|
||||
<Button
|
||||
onClick={onClickNext}
|
||||
loading={newProjectedLoading}
|
||||
disabled={newProjectedLoading || !canSubmit}
|
||||
loading={isCreatingNewProject}
|
||||
disabled={isCreatingNewProject || !canSubmit}
|
||||
>
|
||||
Create new project
|
||||
</Button>
|
||||
|
||||
+10
-23
@@ -1,4 +1,3 @@
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
@@ -6,10 +5,8 @@ import { Button, Input, Listbox } from 'ui'
|
||||
|
||||
import { WizardLayout } from 'components/layouts'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { invalidateOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { useOrganizationCreateMutation } from 'data/organizations/organization-create-mutation'
|
||||
import { useStore } from 'hooks'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
|
||||
const ORG_KIND_TYPES = {
|
||||
@@ -35,14 +32,19 @@ const ORG_SIZE_DEFAULT = '1'
|
||||
* No org selected yet, create a new one
|
||||
*/
|
||||
const Wizard: NextPageWithLayout = () => {
|
||||
const queryClient = useQueryClient()
|
||||
const { ui } = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
const [orgName, setOrgName] = useState('')
|
||||
const [orgKind, setOrgKind] = useState(ORG_KIND_DEFAULT)
|
||||
const [orgSize, setOrgSize] = useState(ORG_SIZE_DEFAULT)
|
||||
const [newOrgLoading, setNewOrgLoading] = useState(false)
|
||||
|
||||
const { mutate: createOrganization, isLoading: newOrgLoading } = useOrganizationCreateMutation({
|
||||
onSuccess: async (org: any) => {
|
||||
// [Joshen] API spec is wrong? its returning org type as only having id and name
|
||||
router.push(`/new/${org.slug}`)
|
||||
},
|
||||
})
|
||||
|
||||
function validateOrgName(name: any) {
|
||||
return name.length >= 1
|
||||
@@ -65,29 +67,14 @@ const Wizard: NextPageWithLayout = () => {
|
||||
const trimmedOrgName = orgName ? orgName.trim() : ''
|
||||
const isOrgNameValid = validateOrgName(trimmedOrgName)
|
||||
if (!isOrgNameValid) {
|
||||
ui.setNotification({ category: 'error', message: 'Organization name is empty' })
|
||||
return
|
||||
return ui.setNotification({ category: 'error', message: 'Organization name is empty' })
|
||||
}
|
||||
|
||||
setNewOrgLoading(true)
|
||||
const response = await post(`${API_URL}/organizations`, {
|
||||
createOrganization({
|
||||
name: trimmedOrgName,
|
||||
kind: orgKind,
|
||||
...(orgKind == 'COMPANY' ? { size: orgSize } : {}),
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
setNewOrgLoading(false)
|
||||
ui.setNotification({
|
||||
error: response.error,
|
||||
category: 'error',
|
||||
message: `Failed to create organization: ${response.error?.message ?? response.error}`,
|
||||
})
|
||||
} else {
|
||||
const org = response
|
||||
await invalidateOrganizationsQuery(queryClient)
|
||||
router.push(`/new/${org.slug}`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import clsx from 'clsx'
|
||||
import { useParams } from 'common'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { IconLoader } from 'ui'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import DeleteHookModal from 'components/interfaces/Database/Hooks/DeleteHookModal'
|
||||
import EditHookPanel from 'components/interfaces/Database/Hooks/EditHookPanel'
|
||||
import HooksList from 'components/interfaces/Database/Hooks/HooksList/HooksList'
|
||||
import { DatabaseLayout } from 'components/layouts'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
|
||||
import NoPermission from 'components/ui/NoPermission'
|
||||
import { useHooksEnableMutation } from 'data/database/hooks-enable-mutation'
|
||||
import { useCheckPermissions, useStore } from 'hooks'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { IconLoader } from 'ui'
|
||||
|
||||
const HooksPage: NextPageWithLayout = () => {
|
||||
const { meta, ui } = useStore()
|
||||
const { project } = useProjectContext()
|
||||
|
||||
const { ref } = useParams()
|
||||
const schemas = meta.schemas.list()
|
||||
@@ -33,24 +30,23 @@ const HooksPage: NextPageWithLayout = () => {
|
||||
const canReadWebhooks = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'triggers')
|
||||
const canCreateWebhooks = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'triggers')
|
||||
|
||||
useEffect(() => {
|
||||
if (ui.selectedProjectRef) meta.hooks.load()
|
||||
}, [ui.selectedProjectRef])
|
||||
|
||||
const enableHooksForProject = async () => {
|
||||
const res = await post(`${API_URL}/database/${ref}/hook-enable`, {})
|
||||
if (res.error) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to enable webhooks: ${res.error.message}`,
|
||||
})
|
||||
} else {
|
||||
const { mutate: enableHooks, isLoading: isEnablingHooks } = useHooksEnableMutation({
|
||||
onSuccess: () => {
|
||||
meta.schemas.load()
|
||||
ui.setNotification({
|
||||
category: 'success',
|
||||
message: `Successfully enabled webhooks`,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (ui.selectedProjectRef) meta.hooks.load()
|
||||
}, [ui.selectedProjectRef])
|
||||
|
||||
const enableHooksForProject = async () => {
|
||||
if (!ref) return console.error('Project ref is required')
|
||||
enableHooks({ ref })
|
||||
}
|
||||
|
||||
const createHook = () => {
|
||||
@@ -89,8 +85,11 @@ const HooksPage: NextPageWithLayout = () => {
|
||||
title="Database Webhooks"
|
||||
ctaButtonLabel="Enable webhooks"
|
||||
onClickCta={() => enableHooksForProject()}
|
||||
disabled={!canCreateWebhooks}
|
||||
disabledMessage="You need additional permissions to enable webhooks"
|
||||
loading={isEnablingHooks}
|
||||
disabled={isEnablingHooks || !canCreateWebhooks}
|
||||
disabledMessage={
|
||||
!canCreateWebhooks ? 'You need additional permissions to enable webhooks' : undefined
|
||||
}
|
||||
>
|
||||
<p className="text-sm text-scale-1100">
|
||||
Database Webhooks can be used to trigger serverless functions or send requests to an
|
||||
|
||||
@@ -2,13 +2,11 @@ import { runInAction } from 'mobx'
|
||||
import { observer, useLocalObservable } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ChangeEvent, createContext, useContext, useEffect, useState } from 'react'
|
||||
import { Button, Input, Select } from 'ui'
|
||||
|
||||
import VercelIntegrationLayout from 'components/layouts/VercelIntegrationLayout'
|
||||
import { useOrganizationCreateMutation } from 'data/organizations/organization-create-mutation'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { useStore } from 'hooks'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { Button, Input, Select } from 'ui'
|
||||
|
||||
const PageContext = createContext(null)
|
||||
function SetupOrg() {
|
||||
@@ -74,38 +72,19 @@ function SetupOrg() {
|
||||
export default observer(SetupOrg)
|
||||
|
||||
const CreateOrganization = observer(({}) => {
|
||||
const PageState: any = useContext(PageContext)
|
||||
const router = useRouter()
|
||||
|
||||
const { ui, app } = useStore()
|
||||
const PageState: any = useContext(PageContext)
|
||||
const { data: organizations } = useOrganizationsQuery()
|
||||
|
||||
const [orgName, setOrgName] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function onClick() {
|
||||
setLoading(true)
|
||||
const response = await post(`${API_URL}/organizations`, {
|
||||
name: orgName,
|
||||
})
|
||||
if (response.error) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to set up organization: ${response.error.message}`,
|
||||
})
|
||||
setLoading(false)
|
||||
} else {
|
||||
const org = response
|
||||
const { mutate: createOrganization, isLoading: isCreating } = useOrganizationCreateMutation({
|
||||
onSuccess: (org) => {
|
||||
PageState.selectedOrg = org
|
||||
|
||||
const query = new URLSearchParams(PageState.queryParams).toString()
|
||||
router.push(`/vercel/setupProject?${query}`)
|
||||
}
|
||||
}
|
||||
|
||||
function onOrgNameChange(e: any) {
|
||||
setOrgName(e.target.value)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
@@ -126,10 +105,14 @@ const CreateOrganization = observer(({}) => {
|
||||
placeholder="Organization name"
|
||||
descriptionText="What's the name of your company or team?"
|
||||
value={orgName}
|
||||
onChange={onOrgNameChange}
|
||||
onChange={(e: any) => setOrgName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button disabled={loading || orgName == ''} loading={loading} onClick={onClick}>
|
||||
<Button
|
||||
disabled={isCreating || orgName === ''}
|
||||
loading={isCreating}
|
||||
onClick={() => createOrganization({ name: orgName })}
|
||||
>
|
||||
Create organization
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { createContext, useEffect, useContext, useRef, useState, ChangeEvent } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { observer, useLocalObservable } from 'mobx-react-lite'
|
||||
import { makeAutoObservable } from 'mobx'
|
||||
import { debounce } from 'lodash'
|
||||
import { Button, Input, Listbox } from 'ui'
|
||||
import { Dictionary } from 'components/grid'
|
||||
import generator from 'generate-password'
|
||||
import { useStore } from 'hooks'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { passwordStrength } from 'lib/helpers'
|
||||
import {
|
||||
PROVIDERS,
|
||||
DEFAULT_MINIMUM_PASSWORD_STRENGTH,
|
||||
API_URL,
|
||||
PRICING_TIER_PRODUCT_IDS,
|
||||
AWS_REGIONS,
|
||||
} from 'lib/constants'
|
||||
import { VERCEL_INTEGRATION_CONFIGS } from 'lib/vercelConfigs'
|
||||
import { debounce } from 'lodash'
|
||||
import { makeAutoObservable } from 'mobx'
|
||||
import { observer, useLocalObservable } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ChangeEvent, createContext, useContext, useEffect, useRef, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { Button, Input, Listbox } from 'ui'
|
||||
|
||||
import { Dictionary } from 'components/grid'
|
||||
import VercelIntegrationLayout from 'components/layouts/VercelIntegrationLayout'
|
||||
import {
|
||||
createVercelEnv,
|
||||
fetchVercelProject,
|
||||
prepareVercelEvns,
|
||||
} from 'components/to-be-cleaned/Integration/Vercel.utils'
|
||||
import VercelIntegrationLayout from 'components/layouts/VercelIntegrationLayout'
|
||||
import Loading from 'components/ui/Loading'
|
||||
import PasswordStrengthBar from 'components/ui/PasswordStrengthBar'
|
||||
import { useProjectCreateMutation } from 'data/projects/project-create-mutation'
|
||||
import {
|
||||
AWS_REGIONS,
|
||||
DEFAULT_MINIMUM_PASSWORD_STRENGTH,
|
||||
PRICING_TIER_PRODUCT_IDS,
|
||||
PROVIDERS,
|
||||
} from 'lib/constants'
|
||||
import { passwordStrength } from 'lib/helpers'
|
||||
import { VERCEL_INTEGRATION_CONFIGS } from 'lib/vercelConfigs'
|
||||
|
||||
interface ISetupProjectStore {
|
||||
token: string
|
||||
@@ -144,19 +143,49 @@ const Connecting = () => (
|
||||
)
|
||||
|
||||
const CreateProject = observer(() => {
|
||||
const _store = useContext(PageContext)
|
||||
const router = useRouter()
|
||||
const { ui } = useStore()
|
||||
const _store = useContext(PageContext)
|
||||
|
||||
const [projectName, setProjectName] = useState('')
|
||||
const [dbPass, setDbPass] = useState('')
|
||||
const [passwordStrengthMessage, setPasswordStrengthMessage] = useState('')
|
||||
const [passwordStrengthScore, setPasswordStrengthScore] = useState(-1)
|
||||
const [dbRegion, setDbRegion] = useState(PROVIDERS.AWS.default_region)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const delayedCheckPasswordStrength = useRef(
|
||||
debounce((value: string) => checkPasswordStrength(value), 300)
|
||||
).current
|
||||
|
||||
const { mutate: createProject, isLoading } = useProjectCreateMutation({
|
||||
onSuccess: async (res) => {
|
||||
const project = { ...res, db_host: `db.${res.ref}.supabase.co`, db_password: dbPass }
|
||||
_store.supabaseProjectRef = project.ref
|
||||
|
||||
const requiredEnvs =
|
||||
VERCEL_INTEGRATION_CONFIGS.find((x) => x.id == _store.externalId)?.envs || []
|
||||
const envs = prepareVercelEvns(requiredEnvs, project as any)
|
||||
|
||||
await Promise.allSettled(
|
||||
envs.map(async (env: any) => {
|
||||
try {
|
||||
const data = await createVercelEnv({
|
||||
...env,
|
||||
vercelProjectId: _store.selectedVercelProject?.id,
|
||||
vercelTeamId: _store.teamId,
|
||||
vercelToken: _store.token,
|
||||
})
|
||||
return data
|
||||
} catch (err) {
|
||||
console.error(`Error: ${err}`)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const query = new URLSearchParams(_store.queryParams).toString()
|
||||
router.push(`/vercel/complete?${query}`)
|
||||
},
|
||||
})
|
||||
|
||||
const canSubmit =
|
||||
projectName != '' &&
|
||||
passwordStrengthScore >= DEFAULT_MINIMUM_PASSWORD_STRENGTH &&
|
||||
@@ -193,67 +222,24 @@ const CreateProject = observer(() => {
|
||||
delayedCheckPasswordStrength(password)
|
||||
}
|
||||
|
||||
async function createSupabaseProject(dbSql: string) {
|
||||
const data = {
|
||||
cloud_provider: PROVIDERS.AWS.id, // hardcoded for DB instances to be under AWS
|
||||
org_id: Number(_store.supabaseOrgId),
|
||||
name: projectName,
|
||||
db_pass: dbPass,
|
||||
db_region: dbRegion,
|
||||
db_sql: dbSql || '',
|
||||
db_pricing_tier_id: PRICING_TIER_PRODUCT_IDS.FREE,
|
||||
auth_site_url: _store.selectedVercelProjectUrl,
|
||||
vercel_configuration_id: _store.configurationId,
|
||||
}
|
||||
const project = await post(`${API_URL}/projects`, data)
|
||||
return { ...project, db_host: `db.${project.ref}.supabase.co`, db_password: dbPass }
|
||||
}
|
||||
|
||||
async function onCreateProject() {
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const requiredEnvs =
|
||||
VERCEL_INTEGRATION_CONFIGS.find((x) => x.id == _store.externalId)?.envs || []
|
||||
const dbSql =
|
||||
VERCEL_INTEGRATION_CONFIGS.find((x) => x.id == _store.externalId)?.template?.sql || ''
|
||||
|
||||
const response = await createSupabaseProject(dbSql)
|
||||
if (response.error) {
|
||||
setLoading(false)
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to create project: ${response.error.message}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const project = response
|
||||
_store.supabaseProjectRef = project.ref
|
||||
|
||||
const envs = prepareVercelEvns(requiredEnvs, project)
|
||||
|
||||
await Promise.allSettled(
|
||||
envs.map(async (env: any) => {
|
||||
try {
|
||||
const data = await createVercelEnv({
|
||||
...env,
|
||||
vercelProjectId: _store.selectedVercelProject?.id,
|
||||
vercelTeamId: _store.teamId,
|
||||
vercelToken: _store.token,
|
||||
})
|
||||
return data
|
||||
} catch (err) {
|
||||
console.error(`Error: ${err}`)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const query = new URLSearchParams(_store.queryParams).toString()
|
||||
router.push(`/vercel/complete?${query}`)
|
||||
createProject({
|
||||
cloudProvider: PROVIDERS.AWS.id,
|
||||
organizationId: Number(_store.supabaseOrgId),
|
||||
name: projectName,
|
||||
dbPass: dbPass,
|
||||
dbRegion: dbRegion,
|
||||
dbSql: dbSql || '',
|
||||
dbPricingTierId: PRICING_TIER_PRODUCT_IDS.FREE,
|
||||
configurationId: _store.configurationId,
|
||||
authSiteUrl: _store.selectedVercelProjectUrl,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error', error)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +308,7 @@ const CreateProject = observer(() => {
|
||||
</Listbox>
|
||||
</div>
|
||||
</div>
|
||||
<Button disabled={loading || !canSubmit} loading={loading} onClick={onCreateProject}>
|
||||
<Button disabled={isLoading || !canSubmit} loading={isLoading} onClick={onCreateProject}>
|
||||
Create project
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user