- Do copy this access token and store it in a secure place - you will not be able to see
- it again.
-
}
+ className="w-7 h-7 absolute top-3 right-3"
+ onClick={onClose}
+ />
+
)
}
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx
index 699e4b8533024..0195c78d2e931 100644
--- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx
+++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx
@@ -16,10 +16,14 @@ import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-que
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import UpcomingInvoice from './UpcomingInvoice'
+import { MANAGED_BY } from 'lib/constants/infrastructure'
+import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
const BillingBreakdown = () => {
const { slug: orgSlug } = useParams()
+ const { data: selectedOrganization } = useSelectedOrganizationQuery()
+
const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } =
useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.subscriptions')
@@ -44,26 +48,43 @@ const BillingBreakdown = () => {
Upcoming Invoice
-
+ {selectedOrganization?.managed_by !== MANAGED_BY.AWS_MARKETPLACE && (
+
+ )}
- Your upcoming invoice (excluding credits) will continue to update until the end of your
- billing cycle on {billingCycleEnd.format('MMMM DD')}. For a more detailed breakdown,
- visit the usage page.
+ {selectedOrganization?.managed_by === MANAGED_BY.AWS_MARKETPLACE ? (
+ <>
+ You'll receive two invoices from AWS Marketplace: one on the 3rd of{' '}
+ {billingCycleEnd.format('MMMM')} for your usage in{' '}
+ {billingCycleStart.format('MMMM')} and one on {billingCycleEnd.format('MMMM DD')}{' '}
+ for the fixed subscription fee.
+ >
+ ) : (
+ <>
+ Your upcoming invoice (excluding credits) will continue to update until the end of
+ your billing cycle on {billingCycleEnd.format('MMMM DD')}.
+ >
+ )}
+ <>
+ {' '}
+ For a more detailed breakdown, visit the{' '}
+ usage page.
+ >
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerData.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerData.tsx
index a137720baa988..54d50a87d7778 100644
--- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerData.tsx
+++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerData.tsx
@@ -118,7 +118,7 @@ export const BillingCustomerData = () => {
{selectedOrganization?.managed_by !== undefined &&
selectedOrganization?.managed_by !== 'supabase' ? (
{
const { slug } = useParams()
const { resolvedTheme } = useTheme()
+ const { data: selectedOrganization } = useSelectedOrganizationQuery()
const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } =
useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.subscriptions')
@@ -47,6 +53,8 @@ const CostControl = ({}: CostControlProps) => {
const canChangeTier =
!projectUpdateDisabled && !['team', 'enterprise'].includes(currentPlan?.id || '')
+ const costControlDisabled = selectedOrganization?.managed_by === MANAGED_BY.AWS_MARKETPLACE
+
return (
<>
@@ -97,7 +105,22 @@ const CostControl = ({}: CostControlProps) => {
{isError && }
- {isSuccess && (
+ {isSuccess && costControlDisabled && (
+
+
+
+
+ The Spend Cap is not available for organizations managed by{' '}
+ {PARTNER_TO_NAME[selectedOrganization?.managed_by]}.
+
+
+ )}
+
+ {isSuccess && !costControlDisabled && (
{['team', 'enterprise'].includes(currentPlan?.id || '') ? (
{
const { slug } = useParams()
@@ -66,9 +67,9 @@ const PaymentMethods = () => {
{selectedOrganization?.managed_by !== undefined &&
- selectedOrganization?.managed_by !== 'supabase' ? (
+ selectedOrganization?.managed_by !== MANAGED_BY.SUPABASE ? (
{
+ if (selectedOrganization.managed_by === MANAGED_BY.VERCEL_MARKETPLACE) {
+ return {
+ installationId: selectedOrganization?.partner_id,
+ path: '/settings',
+ message: 'Change Plan on Vercel Marketplace',
+ }
+ }
+ if (selectedOrganization.managed_by === MANAGED_BY.AWS_MARKETPLACE) {
+ return {
+ organizationSlug: selectedOrganization?.slug,
+ }
+ }
+}
const PlanUpdateSidePanel = () => {
const router = useRouter()
const { slug } = useParams()
@@ -144,16 +160,11 @@ const PlanUpdateSidePanel = () => {
}
>
- {selectedOrganization?.managed_by === 'vercel-marketplace' && (
+ {selectedOrganization && selectedOrganization.managed_by !== MANAGED_BY.SUPABASE && (
)}
@@ -216,8 +227,10 @@ const PlanUpdateSidePanel = () => {
disabled={
subscription?.plan?.id === 'enterprise' ||
// Downgrades to free are still allowed through the dashboard given we have much better control about showing customers the impact + any possible issues with downgrading to free
- (selectedOrganization?.managed_by !== 'supabase' &&
+ (selectedOrganization?.managed_by !== MANAGED_BY.SUPABASE &&
plan.id !== 'tier_free') ||
+ // Orgs managed by AWS marketplace are not allowed to change the plan
+ selectedOrganization?.managed_by === MANAGED_BY.AWS_MARKETPLACE ||
hasOrioleProjects ||
!canUpdateSubscription
}
@@ -243,7 +256,10 @@ const PlanUpdateSidePanel = () => {
? 'Your organization has projects that are using the OrioleDB extension which is only available on the Free plan. Remove all OrioleDB projects before changing your plan.'
: !canUpdateSubscription
? 'You do not have permission to change the subscription plan'
- : undefined,
+ : selectedOrganization?.managed_by ===
+ MANAGED_BY.AWS_MARKETPLACE
+ ? 'You cannot change the plan for an organization managed by AWS Marketplace'
+ : undefined,
},
}}
>
diff --git a/apps/studio/components/interfaces/Organization/GeneralSettings/OrganizationDeletePanel.tsx b/apps/studio/components/interfaces/Organization/GeneralSettings/OrganizationDeletePanel.tsx
index c963d1006c533..04e6537c15ca1 100644
--- a/apps/studio/components/interfaces/Organization/GeneralSettings/OrganizationDeletePanel.tsx
+++ b/apps/studio/components/interfaces/Organization/GeneralSettings/OrganizationDeletePanel.tsx
@@ -3,6 +3,7 @@ import PartnerManagedResource from 'components/ui/PartnerManagedResource'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { Admonition } from 'ui-patterns'
import { DeleteOrganizationButton } from './DeleteOrganizationButton'
+import { MANAGED_BY } from 'lib/constants/infrastructure'
const OrganizationDeletePanel = () => {
const { data: selectedOrganization } = useSelectedOrganizationQuery()
@@ -20,7 +21,7 @@ const OrganizationDeletePanel = () => {
) : (
{
+ if (selectedOrganization.managed_by === MANAGED_BY.VERCEL_MARKETPLACE) {
+ return {
+ installationId: selectedOrganization?.partner_id,
+ path: '/invoices',
+ }
+ }
+ if (selectedOrganization.managed_by === MANAGED_BY.AWS_MARKETPLACE) {
+ return {
+ organizationSlug: selectedOrganization?.slug,
+ overrideUrl: 'https://console.aws.amazon.com/billing/home#/bills',
+ }
+ }
+}
const InvoicesSettings = () => {
const [page, setPage] = useState(1)
@@ -62,13 +78,9 @@ const InvoicesSettings = () => {
) {
return (
)
}
diff --git a/apps/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx b/apps/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx
index 258a65f1dfb52..b81e86ba18df2 100644
--- a/apps/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx
+++ b/apps/studio/components/interfaces/Settings/Logs/Logs.DatePickers.tsx
@@ -1,13 +1,14 @@
/* eslint-disable react-hooks/exhaustive-deps */
import dayjs from 'dayjs'
-import { ChevronLeft, ChevronRight, Clock, HistoryIcon, XIcon } from 'lucide-react'
+import { ChevronLeft, ChevronRight, Clock, HistoryIcon } from 'lucide-react'
import { PropsWithChildren, useEffect, useRef, useState } from 'react'
import DatePicker from 'react-datepicker'
-import { Label } from '@ui/components/shadcn/ui/label'
import { Badge } from '@ui/components/shadcn/ui/badge'
+import { Label } from '@ui/components/shadcn/ui/label'
import { RadioGroup, RadioGroupItem } from '@ui/components/shadcn/ui/radio-group'
-import TimeSplitInput from 'components/ui/DatePicker/TimeSplitInput'
+import { ButtonTooltip } from 'components/ui/ButtonTooltip'
+import { TimeSplitInput } from 'components/ui/DatePicker/TimeSplitInput'
import { useCurrentOrgPlan } from 'hooks/misc/useCurrentOrgPlan'
import {
Button,
@@ -20,7 +21,6 @@ import {
} from 'ui'
import { LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD } from './Logs.constants'
import type { DatetimeHelper } from './Logs.types'
-import { ButtonTooltip } from 'components/ui/ButtonTooltip'
export type DatePickerValue = {
to: string
diff --git a/apps/studio/components/layouts/OrganizationLayout.tsx b/apps/studio/components/layouts/OrganizationLayout.tsx
index a705d2c6d3fca..ce196c37f72c9 100644
--- a/apps/studio/components/layouts/OrganizationLayout.tsx
+++ b/apps/studio/components/layouts/OrganizationLayout.tsx
@@ -7,12 +7,33 @@ import { useVercelRedirectQuery } from 'data/integrations/vercel-redirect-query'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { withAuth } from 'hooks/misc/withAuth'
import { Alert_Shadcn_, AlertTitle_Shadcn_, Button, cn } from 'ui'
+import { useAwsRedirectQuery } from 'data/integrations/aws-redirect-query'
+import { MANAGED_BY } from 'lib/constants/infrastructure'
const OrganizationLayoutContent = ({ children }: PropsWithChildren<{}>) => {
const { data: selectedOrganization } = useSelectedOrganizationQuery()
- const { data, isSuccess } = useVercelRedirectQuery({
- installationId: selectedOrganization?.partner_id,
- })
+
+ const vercelQuery = useVercelRedirectQuery(
+ {
+ installationId: selectedOrganization?.partner_id,
+ },
+ {
+ enabled: selectedOrganization?.managed_by === MANAGED_BY.VERCEL_MARKETPLACE,
+ }
+ )
+
+ const awsQuery = useAwsRedirectQuery(
+ {
+ organizationSlug: selectedOrganization?.slug,
+ },
+ {
+ enabled: selectedOrganization?.managed_by === MANAGED_BY.AWS_MARKETPLACE,
+ }
+ )
+
+ // Select the appropriate query based on partner
+ const { data, isSuccess } =
+ selectedOrganization?.managed_by === MANAGED_BY.AWS_MARKETPLACE ? awsQuery : vercelQuery
return (
diff --git a/apps/studio/components/ui/DatePicker/DatePicker.tsx b/apps/studio/components/ui/DatePicker/DatePicker.tsx
index 85e722f88e7ee..52a2825fbf894 100644
--- a/apps/studio/components/ui/DatePicker/DatePicker.tsx
+++ b/apps/studio/components/ui/DatePicker/DatePicker.tsx
@@ -1,7 +1,7 @@
import { format } from 'date-fns'
import dayjs from 'dayjs'
import { ArrowRight, Calendar, ChevronLeft, ChevronRight } from 'lucide-react'
-import React, { useEffect, useState } from 'react'
+import { ReactNode, useEffect, useState } from 'react'
import ReactDatePicker from 'react-datepicker'
import type { DatePickerToFrom } from 'components/interfaces/Settings/Logs/Logs.types'
@@ -13,7 +13,7 @@ import {
Popover_Shadcn_,
} from 'ui'
import { ButtonProps } from 'ui/src/components/Button/Button'
-import TimeSplitInput from './TimeSplitInput'
+import { TimeSplitInput } from './TimeSplitInput'
export interface DatePickerProps {
onChange?: (args: DatePickerToFrom) => void
@@ -22,13 +22,15 @@ export interface DatePickerProps {
triggerButtonType?: ButtonProps['type']
triggerButtonClassName?: string
triggerButtonTitle?: string
+ triggerButtonSize?: 'tiny' | 'small'
+ contentSide?: 'bottom' | 'top'
minDate?: Date
maxDate?: Date
hideTime?: boolean
hideClear?: boolean
selectsRange?: boolean
- renderFooter?: (args: DatePickerToFrom) => React.ReactNode | void
- children?: React.ReactNode | React.ReactNode[] | null
+ renderFooter?: (args: DatePickerToFrom) => ReactNode | void
+ children?: ReactNode | ReactNode[] | null
}
const START_DATE_DEFAULT = new Date()
@@ -37,13 +39,15 @@ const END_DATE_DEFAULT = new Date()
const START_TIME_DEFAULT = { HH: '00', mm: '00', ss: '00' }
const END_TIME_DEFAULT = { HH: '23', mm: '59', ss: '59' }
-function DatePicker({
+export function DatePicker({
to,
from,
onChange,
triggerButtonType = 'default',
triggerButtonClassName = '',
triggerButtonTitle,
+ triggerButtonSize,
+ contentSide = 'bottom',
minDate,
maxDate,
hideTime = false,
@@ -147,6 +151,7 @@ function DatePicker({
title={triggerButtonTitle}
type={triggerButtonType}
icon={
}
+ size={triggerButtonSize}
className={triggerButtonClassName}
>
{children !== undefined ? (
@@ -171,14 +176,14 @@ function DatePicker({
)}
-
+
<>
{hideTime ? null : (
<>
-
+
{!selectsRange ? null : (
<>
-
+
-
+
>
)}
-
+
) => {
event.target.select()
setFocus(true)
+ // Prevent parent dialog from stealing focus
+ event.stopPropagation()
}
- useEffect(() => {
- handleOnBlur()
- }, [startDate, endDate])
+ const handleClick = (event: React.MouseEvent) => {
+ event.stopPropagation()
+ }
+
+ const handleKeyDown = (event: React.KeyboardEvent) => {
+ // Allow only numbers and navigation keys
+ const allowedKeys = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter']
+ const isNumber = /^[0-9]$/.test(event.key)
+
+ if (!isNumber && !allowedKeys.includes(event.key)) {
+ event.preventDefault()
+ }
+
+ // Prevent parent dialog from stealing focus on keydown
+ event.stopPropagation()
+ }
+
+ const handleInput = (event: React.FormEvent) => {
+ // Prevent parent dialog from stealing focus on input
+ event.stopPropagation()
+ }
function handlePaste(event: ClipboardEvent) {
event.preventDefault()
@@ -206,6 +232,7 @@ const TimeSplitInput = ({
flex h-7 items-center justify-center
gap-0 rounded border border-strong bg-surface-100 text-xs text-foreground-light
${focus && ' border-stronger outline outline-2 outline-border'}
+ hover:border-stronger transition-colors
`}
>
@@ -216,21 +243,14 @@ const TimeSplitInput = ({
type="text"
onBlur={() => handleOnBlur()}
onFocus={handleFocus}
+ onClick={handleClick}
+ onKeyDown={handleKeyDown}
+ onInput={handleInput}
pattern="[0-23]*"
placeholder="00"
onChange={(e) => handleOnChange(e.target.value, 'HH')}
aria-label="Hours"
- className="
- ring-none
- w-4
- border-none
- bg-transparent
- p-0 text-center text-xs
- text-foreground
- outline-none
- ring-0
- focus:ring-0
- "
+ className={inputStyle}
value={time.HH}
/>
:
@@ -238,21 +258,14 @@ const TimeSplitInput = ({
type="text"
onBlur={() => handleOnBlur()}
onFocus={handleFocus}
- pattern="[0-12]*"
+ onClick={handleClick}
+ onKeyDown={handleKeyDown}
+ onInput={handleInput}
+ pattern="[0-59]*"
placeholder="00"
onChange={(e) => handleOnChange(e.target.value, 'mm')}
aria-label="Minutes"
- className="
- ring-none
- w-4
- border-none
- bg-transparent
- p-0 text-center text-xs
- text-foreground
- outline-none
- ring-0
- focus:ring-0
- "
+ className={inputStyle}
value={time.mm}
/>
:
@@ -260,25 +273,16 @@ const TimeSplitInput = ({
type="text"
onBlur={() => handleOnBlur()}
onFocus={handleFocus}
+ onClick={handleClick}
+ onKeyDown={handleKeyDown}
+ onInput={handleInput}
pattern="[0-59]*"
placeholder="00"
onChange={(e) => handleOnChange(e.target.value, 'ss')}
aria-label="Seconds"
- className="
- ring-none
- w-4
- border-none
- bg-transparent
- p-0 text-center text-xs
- text-foreground
- outline-none
- ring-0
- focus:ring-0
- "
+ className={inputStyle}
value={time.ss}
/>
)
}
-
-export default TimeSplitInput
diff --git a/apps/studio/components/ui/PartnerIcon.tsx b/apps/studio/components/ui/PartnerIcon.tsx
index f4c3ff3fa032c..acd3aa7b9529b 100644
--- a/apps/studio/components/ui/PartnerIcon.tsx
+++ b/apps/studio/components/ui/PartnerIcon.tsx
@@ -1,5 +1,7 @@
import { Organization } from 'types'
import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
+import { MANAGED_BY } from 'lib/constants/infrastructure'
+import { PARTNER_TO_NAME } from './PartnerManagedResource'
interface PartnerIconProps {
organization: Pick
@@ -8,27 +10,71 @@ interface PartnerIconProps {
size?: 'small' | 'medium' | 'large'
}
+function getPartnerIcon(
+ organization: Pick,
+ size: 'small' | 'medium' | 'large'
+) {
+ switch (organization.managed_by) {
+ case MANAGED_BY.VERCEL_MARKETPLACE:
+ return (
+
+ )
+ case MANAGED_BY.AWS_MARKETPLACE:
+ return (
+
+ )
+ default:
+ return null
+ }
+}
+
function PartnerIcon({
organization,
showTooltip = true,
- tooltipText = 'This organization is managed by Vercel Marketplace.',
+ tooltipText,
size = 'small',
}: PartnerIconProps) {
- if (organization.managed_by === 'vercel-marketplace') {
- const icon = (
-
- )
+ if (
+ organization.managed_by === MANAGED_BY.VERCEL_MARKETPLACE ||
+ organization.managed_by === MANAGED_BY.AWS_MARKETPLACE
+ ) {
+ const icon = getPartnerIcon(organization, size)
if (!showTooltip) {
return (
@@ -45,6 +91,8 @@ function PartnerIcon({
)
}
+ const defaultTooltipText = `This organization is managed by ${PARTNER_TO_NAME[organization.managed_by]}`
+
return (
@@ -59,7 +107,7 @@ function PartnerIcon({
{icon}
-
{tooltipText}
+
{tooltipText ?? defaultTooltipText}
)
}
diff --git a/apps/studio/components/ui/PartnerManagedResource.tsx b/apps/studio/components/ui/PartnerManagedResource.tsx
index 2acd744ed9a41..137a7b9b7ffff 100644
--- a/apps/studio/components/ui/PartnerManagedResource.tsx
+++ b/apps/studio/components/ui/PartnerManagedResource.tsx
@@ -1,15 +1,18 @@
import { ExternalLink } from 'lucide-react'
import { useVercelRedirectQuery } from 'data/integrations/vercel-redirect-query'
+import { useAwsRedirectQuery } from 'data/integrations/aws-redirect-query'
import { Alert_Shadcn_, AlertTitle_Shadcn_, Button } from 'ui'
import PartnerIcon from './PartnerIcon'
import { MANAGED_BY, ManagedBy } from 'lib/constants/infrastructure'
interface PartnerManagedResourceProps {
- partner: ManagedBy
+ managedBy: ManagedBy
resource: string
cta?: {
installationId?: string
+ organizationSlug?: string
+ overrideUrl?: string
path?: string
message?: string
}
@@ -21,35 +24,47 @@ export const PARTNER_TO_NAME = {
[MANAGED_BY.SUPABASE]: 'Supabase',
} as const
-function PartnerManagedResource({ partner, resource, cta }: PartnerManagedResourceProps) {
- const isManagedBySupabase = partner === MANAGED_BY.SUPABASE
+function PartnerManagedResource({ managedBy, resource, cta }: PartnerManagedResourceProps) {
const ctaEnabled = cta !== undefined
- const { data, isLoading, isError } = useVercelRedirectQuery(
+ // Use appropriate redirect query based on partner
+ const vercelQuery = useVercelRedirectQuery(
{
installationId: cta?.installationId,
},
{
- enabled: ctaEnabled && !isManagedBySupabase,
+ enabled: ctaEnabled && managedBy === MANAGED_BY.VERCEL_MARKETPLACE,
}
)
- if (isManagedBySupabase) return null
+ const awsQuery = useAwsRedirectQuery(
+ {
+ organizationSlug: cta?.organizationSlug,
+ },
+ {
+ enabled: ctaEnabled && managedBy === MANAGED_BY.AWS_MARKETPLACE,
+ }
+ )
+
+ if (managedBy === MANAGED_BY.SUPABASE) return null
+
+ const { data, isLoading, isError } =
+ managedBy === MANAGED_BY.VERCEL_MARKETPLACE ? vercelQuery : awsQuery
const ctaUrl = (data?.url ?? '') + (cta?.path ?? '')
return (
-
+
- {resource} are managed by {PARTNER_TO_NAME[partner]}.
+ {resource} are managed by {PARTNER_TO_NAME[managedBy]}.
{ctaEnabled && (
} disabled={isLoading || isError}>
-
- {cta.message || `View ${resource} on ${PARTNER_TO_NAME[partner]}`}
+
+ {cta.message || `View ${resource} on ${PARTNER_TO_NAME[managedBy]}`}
)}
diff --git a/apps/studio/data/access-tokens/access-tokens-create-mutation.ts b/apps/studio/data/access-tokens/access-tokens-create-mutation.ts
index f7aa9910e92a6..c4f2d182383ac 100644
--- a/apps/studio/data/access-tokens/access-tokens-create-mutation.ts
+++ b/apps/studio/data/access-tokens/access-tokens-create-mutation.ts
@@ -8,9 +8,9 @@ import { accessTokenKeys } from './keys'
export type AccessTokenCreateVariables = components['schemas']['CreateAccessTokenBody']
-export async function createAccessToken({ name, scope }: AccessTokenCreateVariables) {
+export async function createAccessToken({ name, scope, expires_at }: AccessTokenCreateVariables) {
const { data, error } = await post('/platform/profile/access-tokens', {
- body: { name, scope },
+ body: { name, scope, expires_at },
})
if (error) handleError(error)
diff --git a/apps/studio/data/integrations/aws-redirect-query.ts b/apps/studio/data/integrations/aws-redirect-query.ts
new file mode 100644
index 0000000000000..e9a030cb258f2
--- /dev/null
+++ b/apps/studio/data/integrations/aws-redirect-query.ts
@@ -0,0 +1,38 @@
+import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+import type { ResponseError } from 'types'
+import { integrationKeys } from './keys'
+import { get, handleError } from 'data/fetchers'
+
+export type AwsRedirectVariables = {
+ organizationSlug?: string
+}
+
+export async function getAwsRedirect(
+ { organizationSlug }: AwsRedirectVariables,
+ signal?: AbortSignal
+) {
+ if (!organizationSlug) throw new Error('organizationSlug is required')
+
+ const { data, error } = await get(`/platform/organizations/{slug}/cloud-marketplace/redirect`, {
+ params: { path: { slug: organizationSlug } },
+ signal,
+ })
+ if (error) handleError(error)
+ return data
+}
+
+export type AwsRedirectData = Awaited>
+export type AwsRedirectError = ResponseError
+
+export const useAwsRedirectQuery = (
+ { organizationSlug }: AwsRedirectVariables,
+ { enabled = true, ...options }: UseQueryOptions = {}
+) =>
+ useQuery(
+ integrationKeys.awsRedirect(organizationSlug),
+ ({ signal }) => getAwsRedirect({ organizationSlug }, signal),
+ {
+ enabled: enabled && typeof organizationSlug !== 'undefined',
+ ...options,
+ }
+ )
diff --git a/apps/studio/data/integrations/keys.ts b/apps/studio/data/integrations/keys.ts
index 1a86cfdfa407e..a90b6fbe3bdfd 100644
--- a/apps/studio/data/integrations/keys.ts
+++ b/apps/studio/data/integrations/keys.ts
@@ -25,4 +25,5 @@ export const integrationKeys = {
githubConnectionsList: (organizationId: number | undefined) =>
['organizations', organizationId, 'github-connections'] as const,
vercelRedirect: (installationId?: string) => ['vercel-redirect', installationId] as const,
+ awsRedirect: (organizationSlug?: string) => ['aws-redirect', organizationSlug] as const,
}
diff --git a/apps/studio/pages/account/tokens.tsx b/apps/studio/pages/account/tokens.tsx
index 89f6e22411178..953e898ab9eb4 100644
--- a/apps/studio/pages/account/tokens.tsx
+++ b/apps/studio/pages/account/tokens.tsx
@@ -32,7 +32,7 @@ const UserAccessTokens: NextPageWithLayout = () => {
- {newToken &&
}
+ {newToken &&
setNewToken(undefined)} />}