AccessTokenScopeList.tsx 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import React from 'react';
  2. import type { Scope } from '@growi/core/dist/interfaces';
  3. import { useTranslation } from 'next-i18next';
  4. import type { UseFormRegisterReturn } from 'react-hook-form';
  5. import { useIsDeviceLargerThanMd } from '~/stores/ui';
  6. import styles from './AccessTokenScopeList.module.scss';
  7. const moduleClass = styles['access-token-scope-list'] ?? '';
  8. interface scopeObject {
  9. [key: string]: Scope | scopeObject;
  10. }
  11. interface AccessTokenScopeListProps {
  12. scopeObject: scopeObject;
  13. register: UseFormRegisterReturn<'scopes'>;
  14. disabledScopes: Set<Scope>
  15. level?: number;
  16. }
  17. /**
  18. * Renders the permission object recursively as nested checkboxes.
  19. */
  20. export const AccessTokenScopeList: React.FC<AccessTokenScopeListProps> = ({
  21. scopeObject,
  22. register,
  23. disabledScopes,
  24. level = 1,
  25. }) => {
  26. const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
  27. // Convert object into an array to determine "first vs. non-first" elements
  28. const entries = Object.entries(scopeObject);
  29. const { t } = useTranslation('commons');
  30. return (
  31. <>
  32. {entries.map(([scopeKey, scopeValue], idx) => {
  33. const showHr = (level === 1 || level === 2) && idx !== 0;
  34. if (typeof scopeValue === 'object') {
  35. return (
  36. <div key={scopeKey} className={moduleClass}>
  37. {showHr && <hr className="my-1" />}
  38. <div className="my-1 row">
  39. <div className="col-md-5 ">
  40. <label className={`form-check-label fw-bold indentation indentation-level-${level}`}>{scopeKey}</label>
  41. </div>
  42. </div>
  43. {/* Render recursively */}
  44. <AccessTokenScopeList
  45. scopeObject={scopeValue as scopeObject}
  46. register={register}
  47. level={level + 1}
  48. disabledScopes={disabledScopes}
  49. />
  50. </div>
  51. );
  52. }
  53. // If it's a string, render a checkbox
  54. return (
  55. <div key={scopeKey} className={`row my-1 ${moduleClass}`}>
  56. <div className="col-md-5 indentation">
  57. <input
  58. className={`form-check-input indentation indentation-level-${level}`}
  59. type="checkbox"
  60. id={scopeValue as string}
  61. disabled={disabledScopes.has(scopeValue)}
  62. value={scopeValue as string}
  63. {...register}
  64. />
  65. <label className="form-check-label ms-2" htmlFor={scopeValue as string}>
  66. {scopeKey}
  67. </label>
  68. </div>
  69. <div className={`col form-text ${isDeviceLargerThanMd ? '' : 'text-end'}`}>{t(`accesstoken_scopes_desc.${scopeKey.replace(/:/g, '.')}`)}</div>
  70. </div>
  71. );
  72. })}
  73. </>
  74. );
  75. };