Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ export const PaymentConfirmation = ({
paymentIntentSecret,
onPaymentIntentConfirm,
onLoadingChange,
paymentMethodId,
onError,
}: {
paymentIntentSecret: string
paymentMethodId: string
onPaymentIntentConfirm: (response: PaymentIntentResult) => void
onLoadingChange: (loading: boolean) => void
onError?: (error: Error) => void
Expand All @@ -22,7 +20,7 @@ export const PaymentConfirmation = ({
if (stripe && paymentIntentSecret) {
onLoadingChange(true)
stripe!
.confirmCardPayment(paymentIntentSecret, { payment_method: paymentMethodId })
.confirmCardPayment(paymentIntentSecret)
.then((res) => {
onPaymentIntentConfirm(res)
onLoadingChange(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,6 @@ export const CreditTopUp = ({ slug }: { slug: string | undefined }) => {
paymentIntentConfirmed(paymentIntentConfirmation)
}
onLoadingChange={(loading) => setPaymentConfirmationLoading(loading)}
paymentMethodId={form.getValues().paymentMethod}
/>
</Elements>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import HCaptcha from '@hcaptcha/react-hcaptcha'
import { includes, without } from 'lodash'
import { useReducer, useRef, useState } from 'react'
import { useReducer, useState } from 'react'
import { toast } from 'sonner'

import { useParams } from 'common'
import { useSendDowngradeFeedbackMutation } from 'data/feedback/exit-survey-send'
import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation'
import type { OrgSubscription } from 'data/subscriptions/types'
import { useFlag } from 'hooks/ui/useFlag'
import { Alert, Button, Input, Modal } from 'ui'
import type { ProjectInfo } from '../../../../../data/projects/projects-query'
Expand All @@ -22,17 +20,14 @@ export interface ExitSurveyModalProps {
// [Joshen] For context - Exit survey is only when going to Free Plan from a paid plan
const ExitSurveyModal = ({ visible, projects, onClose }: ExitSurveyModalProps) => {
const { slug } = useParams()
const captchaRef = useRef<HCaptcha>(null)

const [message, setMessage] = useState('')
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
const [selectedReasons, dispatchSelectedReasons] = useReducer(reducer, [])

const subscriptionUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
const { mutate: updateOrgSubscription, isLoading: isUpdating } = useOrgSubscriptionUpdateMutation(
{
onError: (error) => {
resetCaptcha()
toast.error(`Failed to downgrade project: ${error.message}`)
},
}
Expand All @@ -55,23 +50,12 @@ const ExitSurveyModal = ({ visible, projects, onClose }: ExitSurveyModalProps) =
}
}

const resetCaptcha = () => {
setCaptchaToken(null)
captchaRef.current?.resetCaptcha()
}

const onSubmit = async () => {
if (selectedReasons.length === 0) {
return toast.error('Please select at least one reason for canceling your subscription')
}

let token = captchaToken

if (!token) {
const captchaResponse = await captchaRef.current?.execute({ async: true })
token = captchaResponse?.response ?? null
await downgradeOrganization()
}
await downgradeOrganization()
}

const downgradeOrganization = async () => {
Expand All @@ -83,7 +67,6 @@ const ExitSurveyModal = ({ visible, projects, onClose }: ExitSurveyModalProps) =
{ slug, tier: 'tier_free' },
{
onSuccess: async () => {
resetCaptcha()
try {
await sendExitSurvey({
orgSlug: slug,
Expand All @@ -110,26 +93,6 @@ const ExitSurveyModal = ({ visible, projects, onClose }: ExitSurveyModalProps) =

return (
<>
<div className="self-center">
<HCaptcha
ref={captchaRef}
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!}
size="invisible"
onVerify={(token) => {
setCaptchaToken(token)
if (document !== undefined) document.body.classList.remove('!pointer-events-auto')
}}
onExpire={() => setCaptchaToken(null)}
onOpen={() => {
// [Joshen] This is to ensure that hCaptcha popup remains clickable
if (document !== undefined) document.body.classList.add('!pointer-events-auto')
}}
onClose={() => {
if (document !== undefined) document.body.classList.remove('!pointer-events-auto')
}}
/>
</div>

<Modal
hideFooter
size="xlarge"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export const SubscriptionPlanUpdateDialog = ({
projects,
}: Props) => {
const { resolvedTheme } = useTheme()
const queryClient = useQueryClient()
const selectedOrganization = useSelectedOrganization()
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string>()
const [paymentIntentSecret, setPaymentIntentSecret] = useState<string | null>(null)
Expand Down Expand Up @@ -182,23 +181,6 @@ export const SubscriptionPlanUpdateDialog = ({
return
}

if (paymentMethod) {
queryClient.setQueriesData(
organizationKeys.paymentMethods(selectedOrganization.slug),
(prev: any) => {
if (!prev) return prev
return {
...prev,
defaultPaymentMethodId: paymentMethod?.id,
data: prev.data.map((pm: any) => ({
...pm,
is_default: pm.id === paymentMethod?.id,
})),
}
}
)
}

// If the user is downgrading from team, should have spend cap disabled by default
const tier =
subscription?.plan?.id === 'team' && selectedTier === PRICING_TIER_PRODUCT_IDS.PRO
Expand Down Expand Up @@ -253,6 +235,10 @@ export const SubscriptionPlanUpdateDialog = ({
<Dialog
open={selectedTier !== undefined && selectedTier !== 'tier_free'}
onOpenChange={(open) => {
// Do not allow closing mid-change
if (isUpdating || paymentConfirmationLoading || isConfirming) {
return
}
if (!open) onClose()
}}
>
Expand Down Expand Up @@ -561,16 +547,16 @@ export const SubscriptionPlanUpdateDialog = ({
</div>

<div className="pt-4">
{!billingViaPartner && !subscriptionPreviewIsLoading && changeType === 'upgrade' && (
{!billingViaPartner && subscriptionPreview != null && changeType === 'upgrade' && (
<div className="space-y-2 mb-4">
<BillingCustomerDataExistingOrgDialog />

<PaymentMethodSelection
ref={paymentMethodSelection}
selectedPaymentMethod={selectedPaymentMethod}
onSelectPaymentMethod={setSelectedPaymentMethod}
onSelectPaymentMethod={() => {}}
createPaymentMethodInline={
subscriptionPreview?.pending_subscription_flow === true
subscriptionPreview.pending_subscription_flow === true
}
readOnly={paymentConfirmationLoading || isConfirming || isUpdating}
/>
Expand Down Expand Up @@ -700,7 +686,6 @@ export const SubscriptionPlanUpdateDialog = ({
paymentIntentConfirmed(paymentIntentConfirmation)
}
onLoadingChange={(loading) => setPaymentConfirmationLoading(loading)}
paymentMethodId={selectedPaymentMethod!}
/>
</Elements>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,6 @@ const NewOrgForm = ({ onPaymentMethodReset, setupIntent, onPlanSelected }: NewOr
paymentIntentConfirmed(paymentIntentConfirmation)
}
onLoadingChange={(loading) => setPaymentConfirmationLoading(loading)}
paymentMethodId={paymentMethod.id}
onError={(err) => {
toast.error(err.message, { duration: 10_000 })
setNewOrgLoading(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,43 +63,17 @@ const StorageExplorer = ({ bucket }: StorageExplorerProps) => {
const currentFolderIdx = openedFolders.length - 1
const currentFolder = openedFolders[currentFolderIdx]

if (itemSearchString) {
if (!currentFolder) {
// At root of bucket
await fetchFolderContents({
bucketId: bucket.id,
folderId: bucket.id,
folderName: bucket.name,
index: -1,
searchString: itemSearchString,
})
} else {
await fetchFolderContents({
bucketId: bucket.id,
folderId: currentFolder.id,
folderName: currentFolder.name,
index: currentFolderIdx,
searchString: itemSearchString,
})
}
} else {
if (!currentFolder) {
// At root of bucket
await fetchFolderContents({
bucketId: bucket.id,
folderId: bucket.id,
folderName: bucket.name,
index: -1,
})
} else {
await fetchFolderContents({
bucketId: bucket.id,
folderId: currentFolder.id,
folderName: currentFolder.name,
index: currentFolderIdx,
})
}
}
const folderId = !currentFolder ? bucket.id : currentFolder.id
const folderName = !currentFolder ? bucket.name : currentFolder.name
const index = !currentFolder ? -1 : currentFolderIdx

await fetchFolderContents({
bucketId: bucket.id,
folderId,
folderName,
index,
searchString: itemSearchString,
})
} else if (view === STORAGE_VIEWS.COLUMNS) {
if (openedFolders.length > 0) {
const paths = openedFolders.map((folder) => folder.name)
Expand All @@ -110,6 +84,7 @@ const StorageExplorer = ({ bucket }: StorageExplorerProps) => {
folderId: bucket.id,
folderName: bucket.name,
index: -1,
searchString: itemSearchString,
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/data/feedback/support-ticket-send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export async function sendSupportTicket({
},
})

if (error) handleError(error)
if (error) handleError(error, { alwaysCapture: true })
return data
}

Expand Down
12 changes: 11 additions & 1 deletion apps/studio/data/fetchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,18 @@ export const {
OPTIONS: options,
} = client

export const handleError = (error: unknown): never => {
type HandleErrorOptions = {
alwaysCapture?: boolean
}

export const handleError = (
error: unknown,
options: HandleErrorOptions = { alwaysCapture: false }
): never => {
if (error && typeof error === 'object') {
if (options.alwaysCapture) {
Sentry.captureException(error)
}
const errorMessage =
'msg' in error && typeof error.msg === 'string'
? error.msg
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ export const useConfirmPendingSubscriptionChangeMutation = ({
queryClient.invalidateQueries(invoicesKeys.orgUpcomingPreview(slug)),
queryClient.invalidateQueries(organizationKeys.detail(slug)),
queryClient.invalidateQueries(organizationKeys.list()),
queryClient.invalidateQueries(organizationKeys.paymentMethods(slug)),
])

await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
Expand Down
14 changes: 14 additions & 0 deletions apps/studio/data/subscriptions/org-subscription-update-mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ export const useOrgSubscriptionUpdateMutation = ({
queryClient.invalidateQueries(organizationKeys.detail(slug)),
queryClient.invalidateQueries(organizationKeys.list()),
])

if (variables.paymentMethod) {
queryClient.setQueriesData(organizationKeys.paymentMethods(slug), (prev: any) => {
if (!prev) return prev
return {
...prev,
defaultPaymentMethodId: variables.paymentMethod,
data: prev.data.map((pm: any) => ({
...pm,
is_default: pm.id === variables.paymentMethod,
})),
}
})
}
}

await onSuccess?.(data, variables, context)
Expand Down
7 changes: 7 additions & 0 deletions docker/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@ POSTGRES_PORT=5432
############
# Supavisor -- Database pooler
############
# Port Supavisor listens on for transaction pooling connections
POOLER_PROXY_PORT_TRANSACTION=6543
# Maximum number of PostgreSQL connections Supavisor opens per pool
POOLER_DEFAULT_POOL_SIZE=20
# Maximum number of client connections Supavisor accepts per pool
POOLER_MAX_CLIENT_CONN=100
# Unique tenant identifier
POOLER_TENANT_ID=your-tenant-id
# Pool size for internal metadata storage used by Supavisor
# This is separate from client connections and used only by Supavisor itself
POOLER_DB_POOL_SIZE=5


############
Expand Down
3 changes: 2 additions & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ services:
POSTGRES_PORT: ${POSTGRES_PORT}
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
DATABASE_URL: ecto://supabase_admin:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/_supabase
DATABASE_URL: ecto://supabase_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/_supabase
CLUSTER_POSTGRES: true
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
VAULT_ENC_KEY: ${VAULT_ENC_KEY}
Expand All @@ -518,6 +518,7 @@ services:
POOLER_DEFAULT_POOL_SIZE: ${POOLER_DEFAULT_POOL_SIZE}
POOLER_MAX_CLIENT_CONN: ${POOLER_MAX_CLIENT_CONN}
POOLER_POOL_MODE: transaction
DB_POOL_SIZE: ${POOLER_DB_POOL_SIZE}
command:
[
"/bin/sh",
Expand Down
Loading