import React, { useEffect, useState } from 'react'; import type { UseFormRegisterReturn } from 'react-hook-form'; import { parseScopes } from '~/client/util/scope-util'; import { useIsAdmin } from '~/stores-universal/context'; import type { Scope } from '../../../interfaces/scope'; import { SCOPE } from '../../../interfaces/scope'; import { AccessTokenScopeList } from './AccessTokenScopeList'; /** * Props for AccessTokenScopeSelect */ type AccessTokenScopeSelectProps = { /** React Hook Form's register function for a field named "scopes" */ register: UseFormRegisterReturn<'scopes'>; watch: string[]; }; /** * Displays a list of permissions in a recursive, nested checkbox interface. */ export const AccessTokenScopeSelect: React.FC = ({ register, watch }) => { const [disabledScopes, setDisabledScopes] = useState>(new Set()); const { data: isAdmin } = useIsAdmin(); const ScopesMap = parseScopes({ scopes: SCOPE, isAdmin }); const extractScopes = (obj: Record): string[] => { let result: string[] = []; Object.values(obj).forEach((value) => { if (typeof value === 'string') { result.push(value); } else if (typeof value === 'object' && !Array.isArray(value)) { result = result.concat(extractScopes(value)); } }); return result; }; const Scopes: string[] = extractScopes(ScopesMap); useEffect(() => { const selectedScopes = watch || []; // Create a set of scopes to disable based on prefixes const disabledSet = new Set(); selectedScopes.forEach((scope) => { // Check if the scope is in the form `xxx:*` if (scope.endsWith(':*')) { // Convert something like `read:*` into the prefix `read:` const prefix = scope.replace(':*', ':'); // Disable all scopes that start with the prefix (but are not the selected scope itself) Scopes.forEach((s:Scope) => { if (s.startsWith(prefix) && s !== scope) { disabledSet.add(s); } }); } }); setDisabledScopes(disabledSet); }, [watch, Scopes]); return (
); };