mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 10:55:06 +03:00
Fix toggling of roles apply to all projects (#34871)
* Store initial state * fixes * Minor --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
1 parent
c3a352331a
commit
9f6faa65bb
4 files changed
+71
-58
No files matched your search
+21
-19
@@ -1,34 +1,36 @@
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { InfoIcon, Check, ExternalLink } from 'lucide-react'
|
||||
import { Check, ExternalLink, InfoIcon } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { useEffect, useState } from 'react'
|
||||
import tweets from 'shared-data/tweets'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { billingPartnerLabel } from 'components/interfaces/Billing/Subscription/Subscription.utils'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { organizationKeys } from 'data/organizations/keys'
|
||||
import { OrganizationBillingSubscriptionPreviewResponse } from 'data/organizations/organization-billing-subscription-preview'
|
||||
import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation'
|
||||
import { SubscriptionTier } from 'data/subscriptions/types'
|
||||
import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
|
||||
import { formatCurrency } from 'lib/helpers'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
Card,
|
||||
CardContent,
|
||||
Badge,
|
||||
} from 'ui'
|
||||
import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation'
|
||||
import { organizationKeys } from 'data/organizations/keys'
|
||||
import { formatCurrency } from 'lib/helpers'
|
||||
import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
|
||||
import { SubscriptionTier } from 'data/subscriptions/types'
|
||||
import { billingPartnerLabel } from 'components/interfaces/Billing/Subscription/Subscription.utils'
|
||||
import PaymentMethodSelection from './PaymentMethodSelection'
|
||||
import { Button, Dialog, DialogContent } from 'ui'
|
||||
import { InfoTooltip } from 'ui-patterns/info-tooltip'
|
||||
import { OrganizationBillingSubscriptionPreviewResponse } from 'data/organizations/organization-billing-subscription-preview'
|
||||
import PaymentMethodSelection from './PaymentMethodSelection'
|
||||
|
||||
const getRandomTweet = () => {
|
||||
const filteredTweets = tweets.filter((it) => it.text.length < 180)
|
||||
@@ -576,7 +578,7 @@ const SubscriptionPlanUpdateDialog = ({
|
||||
onClick={onUpdateSubscription}
|
||||
className="flex-1"
|
||||
>
|
||||
Confirm {planMeta?.change_type === 'downgrade' ? 'Downgrade' : 'Upgrade'}
|
||||
Confirm {planMeta?.change_type === 'downgrade' ? 'downgrade' : 'upgrade'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -219,7 +219,7 @@ export const UpdateRolesConfirmationModal = ({
|
||||
return (
|
||||
<li key={`update-${i}`} className="text-sm text-foreground-light">
|
||||
From <span className="text-foreground">{originalRoleName}</span> to{' '}
|
||||
<span className="text-foreground">{updatedRole?.name}</span> on{' '}
|
||||
<span className="text-foreground">{updatedRole?.name ?? 'Unknown'}</span> on{' '}
|
||||
<span className={project !== undefined ? 'text-foreground' : ''}>
|
||||
{project?.name ?? 'organization'}
|
||||
</span>
|
||||
|
||||
+47
-36
@@ -3,6 +3,7 @@ import { X } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
|
||||
import { DocsButton } from 'components/ui/DocsButton'
|
||||
import { useOrganizationRolesV2Query } from 'data/organization-members/organization-roles-query'
|
||||
import { OrganizationMember } from 'data/organizations/organization-members-query'
|
||||
@@ -85,21 +86,26 @@ export const UpdateRolesPanel = ({ visible, member, onClose }: UpdateRolesPanelP
|
||||
allRoles !== undefined
|
||||
? formatMemberRoleToProjectRoleConfiguration(member, allRoles, projects ?? [])
|
||||
: []
|
||||
const originalConfigurationType =
|
||||
originalConfiguration.length === 1 &&
|
||||
!!orgScopedRoles.find((r) => r.id === originalConfiguration[0].roleId)
|
||||
? 'org-scope'
|
||||
: 'project-scope'
|
||||
|
||||
const orgProjects = (projects ?? []).filter((p) => p.organization_id === organization?.id)
|
||||
const isApplyingRoleToAllProjects =
|
||||
projectsRoleConfiguration.length === 1 && projectsRoleConfiguration[0]?.ref === undefined
|
||||
const canSaveRoles = projectsRoleConfiguration.length > 0
|
||||
|
||||
const lowerPermissionsRole = orgScopedRoles.find((r) => r.name === 'Developer')?.id
|
||||
const sortByObject: any = ['Owner', 'Administrator', 'Developer'].reduce((obj, item, index) => {
|
||||
return { ...obj, [item]: index }
|
||||
}, {})
|
||||
const noAccessProjects = orgProjects.filter((project) => {
|
||||
return !projectsRoleConfiguration.some((p) => p.ref === project.ref)
|
||||
})
|
||||
const numberOfProjectsWithAccess = orgProjects.length - noAccessProjects.length
|
||||
const numberOfAccessHasChanges = originalConfiguration.length !== noAccessProjects.length
|
||||
|
||||
const hasNoChanges = isEqual(projectsRoleConfiguration, originalConfiguration)
|
||||
|
||||
const onSelectProject = (ref: string) => {
|
||||
setProjectsRoleConfiguration(
|
||||
projectsRoleConfiguration.concat({
|
||||
@@ -131,17 +137,27 @@ export const UpdateRolesPanel = ({ visible, member, onClose }: UpdateRolesPanelP
|
||||
}
|
||||
}
|
||||
|
||||
const onToggleApplyToAllProjects = () => {
|
||||
const roleIdToApply =
|
||||
projectsRoleConfiguration[0]?.roleId ?? lowerPermissionsRole ?? orgScopedRoles[0].id
|
||||
if (isApplyingRoleToAllProjects) {
|
||||
setProjectsRoleConfiguration(
|
||||
orgProjects.map((p) => {
|
||||
return { ref: p.ref, projectId: p.id, roleId: roleIdToApply }
|
||||
})
|
||||
)
|
||||
const onToggleApplyToAllProjects = (isApplyAllProjects: boolean) => {
|
||||
const roleIdToApply = lowerPermissionsRole ?? orgScopedRoles[0].id
|
||||
|
||||
if (isApplyAllProjects) {
|
||||
if (originalConfigurationType === 'org-scope') {
|
||||
console.log('Reset: Org scoped')
|
||||
setProjectsRoleConfiguration(originalConfiguration)
|
||||
} else {
|
||||
setProjectsRoleConfiguration([{ ref: undefined, roleId: roleIdToApply }])
|
||||
}
|
||||
} else {
|
||||
setProjectsRoleConfiguration([{ ref: undefined, roleId: roleIdToApply }])
|
||||
if (originalConfigurationType === 'project-scope') {
|
||||
console.log('Reset: Project scoped')
|
||||
setProjectsRoleConfiguration(originalConfiguration)
|
||||
} else {
|
||||
setProjectsRoleConfiguration(
|
||||
orgProjects.map((p) => {
|
||||
return { ref: p.ref, projectId: p.id, roleId: roleIdToApply }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +291,7 @@ export const UpdateRolesPanel = ({ visible, member, onClose }: UpdateRolesPanelP
|
||||
<SelectItem_Shadcn_
|
||||
key={role.id}
|
||||
value={role.id.toString()}
|
||||
className="text-sm"
|
||||
className="text-sm hover:bg-selection cursor-pointer"
|
||||
disabled={!canAssignRole}
|
||||
>
|
||||
{role.name}
|
||||
@@ -288,22 +304,21 @@ export const UpdateRolesPanel = ({ visible, member, onClose }: UpdateRolesPanelP
|
||||
)}
|
||||
|
||||
{!isApplyingRoleToAllProjects && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="text"
|
||||
disabled={!canRemoveRole}
|
||||
className="px-1"
|
||||
icon={<X />}
|
||||
onClick={() => onRemoveProject(project?.ref)}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
{!canRemoveRole && (
|
||||
<TooltipContent side="bottom">
|
||||
Additional permission required to remove role from member
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
<ButtonTooltip
|
||||
type="text"
|
||||
disabled={!canRemoveRole}
|
||||
className="px-1"
|
||||
icon={<X />}
|
||||
onClick={() => onRemoveProject(project?.ref)}
|
||||
tooltip={{
|
||||
content: {
|
||||
side: 'bottom',
|
||||
text: !canRemoveRole
|
||||
? 'Additional permission required to remove role from member'
|
||||
: 'Remove access to project',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -363,13 +378,9 @@ export const UpdateRolesPanel = ({ visible, member, onClose }: UpdateRolesPanelP
|
||||
</Button>
|
||||
<Button
|
||||
loading={false}
|
||||
disabled={!canSaveRoles}
|
||||
disabled={!canSaveRoles || hasNoChanges}
|
||||
onClick={() => {
|
||||
if (isEqual(projectsRoleConfiguration, originalConfiguration)) {
|
||||
onClose()
|
||||
} else {
|
||||
setShowConfirmation(true)
|
||||
}
|
||||
setShowConfirmation(true)
|
||||
}}
|
||||
>
|
||||
Save roles
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
DialogTitle,
|
||||
cn,
|
||||
} from 'ui'
|
||||
import { Admonition } from './../admonition'
|
||||
import { DialogDescription, DialogHeader } from 'ui/src/components/shadcn/ui/dialog'
|
||||
import { Admonition } from './../admonition'
|
||||
|
||||
export interface ConfirmationModalProps {
|
||||
loading?: boolean
|
||||
@@ -85,7 +85,7 @@ const ConfirmationModal = forwardRef<
|
||||
>
|
||||
<DialogContent ref={ref} className="p-0 gap-0 pb-5 !block" size={size}>
|
||||
<DialogHeader className={cn('border-b')} padding={'small'}>
|
||||
<DialogTitle className="">{title}</DialogTitle>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
{description && <DialogDescription>{description}</DialogDescription>}
|
||||
</DialogHeader>
|
||||
{alert && (
|
||||
|
||||
Reference in new issue
Block a user