Skip to content

Commit 977b867

Browse files
ivasilovw3b6x9
andauthored
fix: During project claim, disable the orgs for choosing who can't add another projects (supabase#39398)
* If the auth id has already been used for approving the same org, ignore the error. * Accidentally committed the wrong code while testing. * Disable the choose org button if the org can't add more free projects. * Hide the tooltip if the condition is met. --------- Co-authored-by: Wen Bo Xie <[email protected]>
1 parent 03c1cc0 commit 977b867

File tree

4 files changed

+84
-30
lines changed

4 files changed

+84
-30
lines changed

apps/studio/components/interfaces/Organization/ProjectClaim/choose-org.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { OrganizationSelector } from 'components/ui/org-selector'
2-
import { Organization } from 'types'
32
import { ProjectClaimLayout } from './layout'
43

54
export interface ProjectClaimChooseOrgProps {
6-
onChoose: (org: Organization) => void
5+
onChoose: (orgSlug: string) => void
76
}
87

98
const MAX_ORGS_TO_SHOW = 5

apps/studio/components/interfaces/Organization/ProjectClaim/confirm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,14 @@ export const ProjectClaimConfirm = ({
4141
const queryClient = useQueryClient()
4242

4343
const { mutateAsync: approveRequest, isLoading: isApproving } =
44-
useApiAuthorizationApproveMutation()
44+
useApiAuthorizationApproveMutation({ onError: () => {} })
4545

4646
const { mutateAsync: claimProject, isLoading: isClaiming } = useOrganizationProjectClaimMutation()
4747

4848
const onClaimProject = async () => {
4949
try {
5050
const response = await approveRequest({ id: auth_id!, slug: selectedOrganization.slug })
51+
5152
await claimProject({
5253
slug: selectedOrganization.slug,
5354
token: claimToken!,

apps/studio/components/ui/org-selector.tsx

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,87 @@ import Link from 'next/link'
33
import { useMemo, useState } from 'react'
44

55
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
6+
import { useFreeProjectLimitCheckQuery } from 'data/organizations/free-project-limit-check-query'
67
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
78
import { parseAsString, useQueryState } from 'nuqs'
89
import { Organization } from 'types'
910
import { Badge, Button, Card, CardHeader, CardTitle, Input_Shadcn_ } from 'ui'
11+
import { ButtonTooltip } from './ButtonTooltip'
1012

1113
export interface ProjectClaimChooseOrgProps {
12-
onSelect: (org: Organization) => void
14+
onSelect: (orgSlug: string) => void
1315
maxOrgsToShow?: number
1416
}
1517

18+
const OrganizationCard = ({
19+
org,
20+
onSelect,
21+
}: {
22+
org: Organization
23+
onSelect: (orgSlug: string) => void
24+
}) => {
25+
const isFreePlan = org.plan?.id === 'free'
26+
const { data: membersExceededLimit, isSuccess } = useFreeProjectLimitCheckQuery(
27+
{ slug: org.slug },
28+
{ enabled: isFreePlan }
29+
)
30+
const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0
31+
const freePlanWithExceedingLimits = isFreePlan && hasMembersExceedingFreeTierLimit
32+
33+
return (
34+
<Card
35+
key={org.id}
36+
className="hover:bg-surface-200 rounded-none first:rounded-t-lg last:rounded-b-lg -mb-px"
37+
>
38+
<CardHeader className="flex flex-row justify-between border-none space-y-0 space-x-2">
39+
<CardTitle className="flex items-center gap-2 min-w-0 flex-1">
40+
<span className="truncate min-w-0" title={org.name}>
41+
{org.name}
42+
</span>
43+
<Badge className="shrink-0">{org.plan?.name}</Badge>
44+
</CardTitle>
45+
<ButtonTooltip
46+
tooltip={{
47+
content: {
48+
text:
49+
isSuccess && freePlanWithExceedingLimits ? (
50+
<div className="space-y-3 w-96 p-2">
51+
<p className="text-sm leading-normal">
52+
The following members have reached their maximum limits for the number of
53+
active free plan projects within organizations where they are an administrator
54+
or owner:
55+
</p>
56+
<ul className="pl-5 list-disc">
57+
{membersExceededLimit.map((member, idx: number) => (
58+
<li key={`member-${idx}`}>
59+
{member.username || member.primary_email} (Limit:{' '}
60+
{member.free_project_limit} free projects)
61+
</li>
62+
))}
63+
</ul>
64+
<p className="text-sm leading-normal">
65+
These members will need to either delete, pause, or upgrade one or more of
66+
these projects before you're able to create a free project within this
67+
organization.
68+
</p>
69+
</div>
70+
) : undefined,
71+
},
72+
}}
73+
size="small"
74+
onClick={() => {
75+
onSelect(org.slug)
76+
}}
77+
className="shrink-0"
78+
disabled={isSuccess && freePlanWithExceedingLimits}
79+
>
80+
Choose
81+
</ButtonTooltip>
82+
</CardHeader>
83+
</Card>
84+
)
85+
}
86+
1687
export function OrganizationSelector({ onSelect, maxOrgsToShow = 5 }: ProjectClaimChooseOrgProps) {
1788
const {
1889
data: organizations = [],
@@ -43,6 +114,11 @@ export function OrganizationSelector({ onSelect, maxOrgsToShow = 5 }: ProjectCla
43114

44115
searchParams.set('returnTo', pathname)
45116

117+
const onSelectOrg = (orgSlug: string) => {
118+
onSelect(orgSlug)
119+
setSearch('')
120+
}
121+
46122
return (
47123
<div className="w-full flex flex-col gap-y-4">
48124
{isLoadingOrgs ? (
@@ -66,29 +142,7 @@ export function OrganizationSelector({ onSelect, maxOrgsToShow = 5 }: ProjectCla
66142
<div className="text-center text-foreground-light py-6">No organizations found.</div>
67143
)}
68144
{filteredOrgs.map((org) => (
69-
<Card
70-
key={org.id}
71-
className="hover:bg-surface-200 rounded-none first:rounded-t-lg last:rounded-b-lg -mb-px"
72-
>
73-
<CardHeader className="flex flex-row justify-between border-none space-y-0 space-x-2">
74-
<CardTitle className="flex items-center gap-2 min-w-0 flex-1">
75-
<span className="truncate min-w-0" title={org.name}>
76-
{org.name}
77-
</span>
78-
<Badge className="shrink-0">{org.plan?.name}</Badge>
79-
</CardTitle>
80-
<Button
81-
size="small"
82-
onClick={() => {
83-
onSelect(org)
84-
setSearch('')
85-
}}
86-
className="shrink-0"
87-
>
88-
Choose
89-
</Button>
90-
</CardHeader>
91-
</Card>
145+
<OrganizationCard key={org.id} org={org} onSelect={onSelectOrg} />
92146
))}
93147
{organizations.length > maxOrgsToShow && !showAll && !search && (
94148
<div className="flex justify-center py-2">

apps/studio/pages/claim-project.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { useOrganizationsQuery } from 'data/organizations/organizations-query'
1313
import { useCustomContent } from 'hooks/custom-content/useCustomContent'
1414
import { withAuth } from 'hooks/misc/withAuth'
1515
import type { NextPageWithLayout } from 'types'
16-
import { Admonition } from 'ui-patterns'
16+
import { Admonition } from 'ui-patterns/admonition'
1717

1818
const ClaimProjectPageLayout = ({ children }: PropsWithChildren) => {
1919
const { appTitle } = useCustomContent(['app:title'])
@@ -92,8 +92,8 @@ const ClaimProjectPage: NextPageWithLayout = () => {
9292
if (step === 'choose-org' || !selectedOrganization) {
9393
return (
9494
<ProjectClaimChooseOrg
95-
onChoose={(org) => {
96-
setSelectedOrgSlug(org.slug)
95+
onChoose={(orgSlug) => {
96+
setSelectedOrgSlug(orgSlug)
9797
setStep('benefits')
9898
}}
9999
/>

0 commit comments

Comments
 (0)