AccessTokenScopeSelect.tsx 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import React, { useEffect, useState } from 'react';
  2. import type { UseFormRegisterReturn } from 'react-hook-form';
  3. import { parseScopes } from '~/client/util/scope-util';
  4. import { useIsAdmin } from '~/stores-universal/context';
  5. import type { Scope } from '../../../interfaces/scope';
  6. import { SCOPE } from '../../../interfaces/scope';
  7. import { AccessTokenScopeList } from './AccessTokenScopeList';
  8. /**
  9. * Props for AccessTokenScopeSelect
  10. */
  11. type AccessTokenScopeSelectProps = {
  12. /** React Hook Form's register function for a field named "scopes" */
  13. register: UseFormRegisterReturn<'scopes'>;
  14. watch: string[];
  15. };
  16. /**
  17. * Displays a list of permissions in a recursive, nested checkbox interface.
  18. */
  19. export const AccessTokenScopeSelect: React.FC<AccessTokenScopeSelectProps> = ({ register, watch }) => {
  20. const [disabledScopes, setDisabledScopes] = useState<Set<Scope>>(new Set());
  21. const { data: isAdmin } = useIsAdmin();
  22. const ScopesMap = parseScopes({ scopes: SCOPE, isAdmin });
  23. const extractScopes = (obj: Record<string, any>): string[] => {
  24. let result: string[] = [];
  25. Object.values(obj).forEach((value) => {
  26. if (typeof value === 'string') {
  27. result.push(value);
  28. }
  29. else if (typeof value === 'object' && !Array.isArray(value)) {
  30. result = result.concat(extractScopes(value));
  31. }
  32. });
  33. return result;
  34. };
  35. const Scopes: string[] = extractScopes(ScopesMap);
  36. useEffect(() => {
  37. const selectedScopes = watch || [];
  38. // Create a set of scopes to disable based on prefixes
  39. const disabledSet = new Set<Scope>();
  40. selectedScopes.forEach((scope) => {
  41. // Check if the scope is in the form `xxx:*`
  42. if (scope.endsWith(':*')) {
  43. // Convert something like `read:*` into the prefix `read:`
  44. const prefix = scope.replace(':*', ':');
  45. // Disable all scopes that start with the prefix (but are not the selected scope itself)
  46. Scopes.forEach((s:Scope) => {
  47. if (s.startsWith(prefix) && s !== scope) {
  48. disabledSet.add(s);
  49. }
  50. });
  51. }
  52. });
  53. setDisabledScopes(disabledSet);
  54. }, [watch, Scopes]);
  55. return (
  56. <div className="border rounded">
  57. <AccessTokenScopeList scopeObject={ScopesMap} register={register} disabledScopes={disabledScopes} />
  58. </div>
  59. );
  60. };