From da89d64e3399c49f3a2c3bd73bf030c4278c5bd6 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:24:34 +0200 Subject: [PATCH] Remove unused code for scoped tokens --- .../AccessTokens/Scoped/Form/BasicInfo.tsx | 140 --------------- .../PermissionResourceSelector.tsx | 90 ---------- .../Scoped/Form/Permissions/Permissions.tsx | 165 ------------------ .../Form/Permissions/Permissions.types.ts | 33 ---- .../Permissions/Permissions.utils.test.ts | 128 -------------- .../Form/Permissions/Permissions.utils.ts | 32 ---- .../Form/ResourceAccess/ResourceAccess.tsx | 116 ------------ .../ResourceAccess/ResourceAccess.types.ts | 31 ---- .../ResourceAccess/ResourceMultiSelector.tsx | 69 -------- .../Form/ResourceAccess/ResourceOption.tsx | 41 ----- 10 files changed, 845 deletions(-) delete mode 100644 apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/BasicInfo.tsx delete mode 100644 apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/PermissionResourceSelector.tsx delete mode 100644 apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.tsx delete mode 100644 apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.types.ts delete mode 100644 apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.utils.test.ts delete mode 100644 apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.utils.ts delete mode 100644 apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceAccess.tsx delete mode 100644 apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceAccess.types.ts delete mode 100644 apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceMultiSelector.tsx delete mode 100644 apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceOption.tsx diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/BasicInfo.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/BasicInfo.tsx deleted file mode 100644 index fdf2d5aabc5..00000000000 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/BasicInfo.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import dayjs from 'dayjs' -import { useEffect, useState } from 'react' -import { Control, ControllerRenderProps } from 'react-hook-form' -import { - FormControl, - FormField, - Input, - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, - WarningIcon, -} from 'ui' -import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' - -import { - CUSTOM_EXPIRY_VALUE, - EXPIRES_AT_OPTIONS, - NON_EXPIRING_TOKEN_VALUE, -} from '../../AccessToken.constants' -import { type TokenFormValues } from '../../AccessToken.schemas' -import { DatePicker } from '@/components/ui/DatePicker' - -interface BasicInfoProps { - control: Control - expirationDate: string - onCustomDateChange?: (date: { date: string } | undefined) => void - onCustomExpiryChange?: (isCustom: boolean) => void -} - -export const BasicInfo = ({ - control, - expirationDate, - onCustomDateChange, - onCustomExpiryChange, -}: BasicInfoProps) => { - const [customDate, setCustomDate] = useState() - const [isCustomSelected, setIsCustomSelected] = useState(false) - - useEffect(() => { - const isCustom = expirationDate === CUSTOM_EXPIRY_VALUE - setIsCustomSelected(isCustom) - onCustomExpiryChange?.(isCustom) - }, [expirationDate, onCustomExpiryChange]) - - const handleCustomDateChange = (date: Date | undefined) => { - setCustomDate(date) - if (date) { - onCustomDateChange?.({ date: date.toISOString() }) - } else { - onCustomDateChange?.(undefined) - } - } - - const handleExpiryChange = ( - value: string, - field: ControllerRenderProps - ) => { - const isCustom = value === CUSTOM_EXPIRY_VALUE - setIsCustomSelected(isCustom) - onCustomExpiryChange?.(isCustom) - field.onChange(value) - } - - return ( -
- ( - - - - - - )} - /> - - ( - -
- - - - {isCustomSelected && ( - { - const selectedDate = date.to || date.from - if (selectedDate) { - handleCustomDateChange(new Date(selectedDate)) - } else { - handleCustomDateChange(undefined) - } - }} - > - {customDate ? `${dayjs(customDate).format('DD MMM, HH:mm')}` : 'Select date'} - - )} -
- {field.value === NON_EXPIRING_TOKEN_VALUE && ( -
- - - Make sure to keep your non-expiring token safe and secure. - -
- )} -
- )} - /> -
- ) -} diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/PermissionResourceSelector.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/PermissionResourceSelector.tsx deleted file mode 100644 index e0d823e77ee..00000000000 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/PermissionResourceSelector.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { Key, Plus } from 'lucide-react' -import { Path, PathValue } from 'react-hook-form' -import { - Button, - Checkbox, - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - Popover, - PopoverContent, - PopoverTrigger, -} from 'ui' - -import { - PermissionResource, - PermissionResourceSelectorProps, - PermissionRow, - PermissionsFormValues, -} from './Permissions.types' -import { togglePermissionResource } from './Permissions.utils' -import { ACCESS_TOKEN_RESOURCES } from '@/components/interfaces/Account/AccessTokens/AccessToken.constants' - -export const PermissionResourceSelector = ({ - open, - onOpenChange, - permissionRows, - setValue, - align = 'center', -}: PermissionResourceSelectorProps) => { - const handleToggleResource = (resource: PermissionResource) => { - const newRows = togglePermissionResource(permissionRows, resource) - setValue( - 'permissionRows' as Path, - newRows as PathValue> - ) - } - - return ( - - - - - - - - - No resources found. - - -
- {ACCESS_TOKEN_RESOURCES.map((resource) => { - const isChecked = permissionRows.some( - (row: PermissionRow) => row.resource === resource.resource - ) - return ( - handleToggleResource(resource)} - className="text-foreground" - > -
- handleToggleResource(resource)} - onClick={(e) => e.stopPropagation()} - /> - -
- - {resource.title} - -
-
-
- ) - })} -
-
-
-
-
-
- ) -} diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.tsx deleted file mode 100644 index f22efce175d..00000000000 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { ChevronDown, RotateCcw, X } from 'lucide-react' -import { Path, PathValue } from 'react-hook-form' -import { Button, Checkbox, Popover, PopoverContent, PopoverTrigger, WarningIcon } from 'ui' - -import { PermissionResourceSelector } from './PermissionResourceSelector' -import { PermissionRow, PermissionsFormValues, PermissionsProps } from './Permissions.types' -import { sortActions } from './Permissions.utils' -import { ACCESS_TOKEN_RESOURCES } from '@/components/interfaces/Account/AccessTokens/AccessToken.constants' -import { formatAccessText } from '@/components/interfaces/Account/AccessTokens/AccessToken.utils' -import { ButtonTooltip } from '@/components/ui/ButtonTooltip' - -export const Permissions = ({ - setValue, - watch, - resourceSearchOpen, - setResourceSearchOpen, -}: PermissionsProps) => { - const permissionRows = (watch('permissionRows' as Path) || []) as PermissionRow[] - - return ( -
-
-
- Configure permissions -
- {permissionRows.length > 0 && ( - { - setValue( - 'permissionRows' as Path, - [] as PathValue> - ) - }} - icon={} - tooltip={{ - content: { - side: 'top', - align: 'center', - alignOffset: -10, - text: 'Reset all permissions', - }, - }} - /> - )} - -
-
- - {permissionRows.length === 0 ? ( -
-

No permissions configured yet.

-
- ) : ( -
- {permissionRows.map((row, index) => { - const selectedResource = ACCESS_TOKEN_RESOURCES.find( - (r) => r.resource === row.resource - ) - return ( -
-
-
-
-
- - {selectedResource?.title} - -
-
-
-
- {selectedResource && ( - - - - - -
- {sortActions(selectedResource.actions).map((action) => ( - - ))} -
-
-
- )} -
-
- {index < permissionRows.length - 1 &&
} -
- ) - })} -
- )} -
- -
- - - Once you've set these permissions, you cannot edit them. - -
-
- ) -} diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.types.ts b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.types.ts deleted file mode 100644 index ffb26fe487a..00000000000 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.types.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { FieldValues, UseFormSetValue, UseFormWatch } from 'react-hook-form' - -export interface PermissionResource { - resource: string - title: string - actions: string[] -} - -export interface PermissionRow { - resource: string - actions: string[] -} - -export interface PermissionsFormValues extends FieldValues { - permissionRows?: PermissionRow[] -} - -export interface PermissionsProps< - TFormValues extends PermissionsFormValues = PermissionsFormValues, -> { - setValue: UseFormSetValue - watch: UseFormWatch - resourceSearchOpen: boolean - setResourceSearchOpen: (open: boolean) => void -} - -export interface PermissionResourceSelectorProps { - open: boolean - onOpenChange: (open: boolean) => void - permissionRows: PermissionRow[] - setValue: UseFormSetValue - align?: 'center' | 'end' | 'start' -} diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.utils.test.ts b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.utils.test.ts deleted file mode 100644 index 35c1cd4b384..00000000000 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.utils.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { describe, expect, it } from 'vitest' - -import type { PermissionResource, PermissionRow } from './Permissions.types' -import { sortActions, togglePermissionResource } from './Permissions.utils' - -// --- sortActions --- - -describe('sortActions', () => { - it('should sort actions by priority order: read, write, create, delete', () => { - expect(sortActions(['delete', 'create', 'write', 'read'])).toEqual([ - 'read', - 'write', - 'create', - 'delete', - ]) - }) - - it('should return empty array for empty input', () => { - expect(sortActions([])).toEqual([]) - }) - - it('should keep a single action as-is', () => { - expect(sortActions(['write'])).toEqual(['write']) - }) - - it('should place unknown actions after the priority ones', () => { - expect(sortActions(['custom-action', 'read', 'delete'])).toEqual([ - 'read', - 'delete', - 'custom-action', - ]) - }) - - it('should preserve relative order of multiple unknown actions', () => { - expect(sortActions(['zeta', 'alpha', 'read'])).toEqual(['read', 'zeta', 'alpha']) - }) - - it('should handle only unknown actions', () => { - expect(sortActions(['foo', 'bar'])).toEqual(['foo', 'bar']) - }) - - it('should handle a subset of priority actions', () => { - expect(sortActions(['delete', 'read'])).toEqual(['read', 'delete']) - }) - - it('should not mutate the original array', () => { - const original = ['delete', 'read'] - sortActions(original) - expect(original).toEqual(['delete', 'read']) - }) -}) - -// --- togglePermissionResource --- - -describe('togglePermissionResource', () => { - const billingResource: PermissionResource = { - resource: 'organization:billing', - title: 'Billing', - actions: ['read', 'write'], - } - - const membersResource: PermissionResource = { - resource: 'organization:members', - title: 'Members', - actions: ['read', 'write', 'create', 'delete'], - } - - const storageResource: PermissionResource = { - resource: 'project:storage', - title: 'Storage', - actions: ['write', 'create'], - } - - it('should add a resource with "read" as the default action when available', () => { - const result = togglePermissionResource([], billingResource) - expect(result).toEqual([{ resource: 'organization:billing', actions: ['read'] }]) - }) - - it('should add a resource with the first action as default when "read" is not available', () => { - const result = togglePermissionResource([], storageResource) - expect(result).toEqual([{ resource: 'project:storage', actions: ['write'] }]) - }) - - it('should remove a resource if it is already in the list', () => { - const existing: PermissionRow[] = [ - { resource: 'organization:billing', actions: ['read'] }, - { resource: 'organization:members', actions: ['read', 'write'] }, - ] - const result = togglePermissionResource(existing, billingResource) - expect(result).toEqual([{ resource: 'organization:members', actions: ['read', 'write'] }]) - }) - - it('should not modify other rows when removing a resource', () => { - const existing: PermissionRow[] = [ - { resource: 'organization:billing', actions: ['read'] }, - { resource: 'organization:members', actions: ['read', 'write'] }, - ] - const result = togglePermissionResource(existing, billingResource) - expect(result).toHaveLength(1) - expect(result[0]).toEqual({ resource: 'organization:members', actions: ['read', 'write'] }) - }) - - it('should append to existing rows when adding', () => { - const existing: PermissionRow[] = [{ resource: 'organization:billing', actions: ['read'] }] - const result = togglePermissionResource(existing, membersResource) - expect(result).toHaveLength(2) - expect(result[1]).toEqual({ resource: 'organization:members', actions: ['read'] }) - }) - - it('should not mutate the original array', () => { - const existing: PermissionRow[] = [{ resource: 'organization:billing', actions: ['read'] }] - const original = [...existing] - togglePermissionResource(existing, membersResource) - expect(existing).toEqual(original) - }) - - it('should toggle off then on again correctly', () => { - const step1 = togglePermissionResource([], billingResource) - expect(step1).toHaveLength(1) - - const step2 = togglePermissionResource(step1, billingResource) - expect(step2).toHaveLength(0) - - const step3 = togglePermissionResource(step2, billingResource) - expect(step3).toHaveLength(1) - expect(step3[0]).toEqual({ resource: 'organization:billing', actions: ['read'] }) - }) -}) diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.utils.ts b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.utils.ts deleted file mode 100644 index cb7547ee431..00000000000 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.utils.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { PermissionResource, PermissionRow } from './Permissions.types' - -export const sortActions = (actions: string[]): string[] => { - const sorted: string[] = [] - const remaining = [...actions] - const priority = ['read', 'write', 'create', 'delete'] - - for (const action of priority) { - const index = remaining.indexOf(action) - if (index !== -1) { - sorted.push(action) - remaining.splice(index, 1) - } - } - - sorted.push(...remaining) - return sorted -} - -export const togglePermissionResource = ( - permissionRows: PermissionRow[], - resource: PermissionResource -): PermissionRow[] => { - const isAlreadyAdded = permissionRows.some((row) => row.resource === resource.resource) - - if (isAlreadyAdded) { - return permissionRows.filter((row) => row.resource !== resource.resource) - } - - const defaultActions = resource.actions.includes('read') ? ['read'] : [resource.actions[0]] - return [...permissionRows, { resource: resource.resource, actions: defaultActions }] -} diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceAccess.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceAccess.tsx deleted file mode 100644 index f14184a9b72..00000000000 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceAccess.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { FormControl, FormField } from 'ui' -import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' - -import { ResourceAccessProps } from './ResourceAccess.types' -import { ResourceMultiSelector } from './ResourceMultiSelector' -import { ResourceOption } from './ResourceOption' -import { useOrgAndProjectData } from '@/components/interfaces/Account/AccessTokens/hooks/useOrgAndProjectData' -import { useProfile } from '@/lib/profile' - -const RESOURCE_OPTIONS = [ - { - value: 'all-orgs' as const, - label: 'Everything', - description: 'Access to all projects across all organizations you have access to.', - }, - { - value: 'selected-orgs' as const, - label: 'Selected orgs', - description: 'Access only to the organizations you have specified.', - }, - { - value: 'selected-projects' as const, - label: 'Selected projects', - description: 'Access only to the projects you have specified.', - }, -] - -export const ResourceAccess = ({ control, resourceAccess, setValue }: ResourceAccessProps) => { - const { profile } = useProfile() - - const { organizations, projects, isLoadingOrgs, isLoadingProjects } = useOrgAndProjectData({ - enabled: !!profile, - }) - - const handleResourceAccessChange = (newValue: string, currentValue: string) => { - if (newValue !== currentValue) { - setValue('selectedOrganizations', []) - setValue('selectedProjects', []) - } - } - - return ( -
- ( - - -
-
- Resource access level - {RESOURCE_OPTIONS.map((option) => ( - { - handleResourceAccessChange(option.value, field.value) - field.onChange(option.value) - }} - /> - ))} -
- -

- {RESOURCE_OPTIONS.find((opt) => opt.value === field.value)?.description} -

-
-
-
- )} - /> - - {resourceAccess === 'selected-orgs' && ( - ( - ({ id: org.slug, name: org.name }))} - isLoading={isLoadingOrgs} - fieldName="selectedOrganizations" - label="Select organizations" - loadingMessage="Loading organizations..." - emptyMessage="No organizations available" - /> - )} - /> - )} - - {resourceAccess === 'selected-projects' && ( - ( - ({ id: project.ref, name: project.name }))} - isLoading={isLoadingProjects} - fieldName="selectedProjects" - label="Select projects" - loadingMessage="Loading projects..." - emptyMessage="No projects available" - /> - )} - /> - )} -
- ) -} diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceAccess.types.ts b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceAccess.types.ts deleted file mode 100644 index 24164866752..00000000000 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceAccess.types.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Control, ControllerRenderProps, UseFormSetValue } from 'react-hook-form' - -import { TokenFormValues } from '@/components/interfaces/Account/AccessTokens/AccessToken.schemas' - -export interface ResourceAccessProps { - control: Control - resourceAccess: string - setValue: UseFormSetValue -} - -export interface ResourceItem { - id: string - name: string -} - -export interface ResourceMultiSelectorProps { - field: ControllerRenderProps - items: ResourceItem[] - isLoading: boolean - fieldName: string - label: string - loadingMessage: string - emptyMessage: string -} - -export interface ResourceOptionProps { - value: string - label: string - isSelected: boolean - onChange: () => void -} diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceMultiSelector.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceMultiSelector.tsx deleted file mode 100644 index dc9fb0b07f4..00000000000 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceMultiSelector.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { useMemo } from 'react' -import { FormControl } from 'ui' -import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' -import { - MultiSelector, - MultiSelectorContent, - MultiSelectorItem, - MultiSelectorList, - MultiSelectorTrigger, -} from 'ui-patterns/multi-select' - -import { ResourceMultiSelectorProps } from './ResourceAccess.types' - -export const ResourceMultiSelector = ({ - field, - items, - isLoading, - fieldName, - label, - loadingMessage, - emptyMessage, -}: ResourceMultiSelectorProps) => { - const idToName = useMemo(() => new Map(items.map((item) => [item.id, item.name])), [items]) - - const displayValues = (field.value || []).map((id: string) => idToName.get(id) || id) - - const handleValuesChange = (names: string[]) => { - const ids = names - .map((name) => { - for (const [id, itemName] of idToName.entries()) { - if (itemName === name) return id - } - return name - }) - .filter(Boolean) - field.onChange(ids) - } - - return ( - - - - - - {isLoading ? ( -
{loadingMessage}
- ) : items.length === 0 ? ( -
{emptyMessage}
- ) : ( - - {items.map((item) => ( - - {item.name} - - ))} - - )} -
-
-
-
- ) -} diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceOption.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceOption.tsx deleted file mode 100644 index 60b3a2f8400..00000000000 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceOption.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Box, Check } from 'lucide-react' -import { cn } from 'ui' - -export const ResourceOption = ({ - value, - label, - isSelected, - onChange, -}: { - value: string - label: string - isSelected: boolean - onChange: () => void -}) => ( - -)