AccessTokenScopeList.tsx 2.9 KB

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