NaokiHigashi28 1 rok temu
rodzic
commit
9856c3dd48

+ 2 - 0
apps/app/src/client/components/Me/AccessTokenForm.tsx

@@ -33,6 +33,7 @@ export const AccessTokenForm = React.memo((props: AccessTokenFormProps): JSX.Ele
     register,
     handleSubmit,
     formState: { errors, isValid },
+    watch,
   } = useForm<FormInputs>({
     defaultValues: {
       expiredAt: defaultExpiredAtStr,
@@ -113,6 +114,7 @@ export const AccessTokenForm = React.memo((props: AccessTokenFormProps): JSX.Ele
               {t('page_me_access_token.scope')}
             </label>
             <AccessTokenScopeSelect
+              watch={watch('scopes')}
               register={register('scopes', {
                 required: 'Please select at least one scope',
               })}

+ 9 - 0
apps/app/src/client/components/Me/AccessTokenScopeList.tsx

@@ -2,6 +2,9 @@ import React from 'react';
 
 import type { UseFormRegisterReturn } from 'react-hook-form';
 
+import type { Scope } from '../../../interfaces/scope';
+
+
 interface scopeObject {
   [key: string]: string | scopeObject;
 }
@@ -9,6 +12,7 @@ interface scopeObject {
 interface AccessTokenScopeListProps {
   scopeObject: scopeObject;
   register: UseFormRegisterReturn<'scopes'>;
+  disabledScopes: Scope[]
   level?: number;
 }
 
@@ -18,8 +22,11 @@ interface AccessTokenScopeListProps {
 export const AccessTokenScopeList: React.FC<AccessTokenScopeListProps> = ({
   scopeObject,
   register,
+  disabledScopes,
   level = 0,
 }) => {
+
+
   // Convert object into an array to determine "first vs. non-first" elements
   const entries = Object.entries(scopeObject);
 
@@ -48,6 +55,7 @@ export const AccessTokenScopeList: React.FC<AccessTokenScopeListProps> = ({
                 scopeObject={scopeValue as scopeObject}
                 register={register}
                 level={level + 1}
+                disabledScopes={disabledScopes}
               />
             </div>
           );
@@ -62,6 +70,7 @@ export const AccessTokenScopeList: React.FC<AccessTokenScopeListProps> = ({
                 style={indentationStyle}
                 type="checkbox"
                 id={scopeValue as string}
+                disabled={disabledScopes.has(scopeValue as string)} // 無効化
                 value={scopeValue as string}
                 {...register}
               />

+ 48 - 3
apps/app/src/client/components/Me/AccessTokenScopeSelect.tsx

@@ -1,9 +1,10 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 
 import type { UseFormRegisterReturn } from 'react-hook-form';
 
 import { parseScopes } from '~/client/util/scope-util';
 
+import type { Scope } from '../../../interfaces/scope';
 import { SCOPE } from '../../../interfaces/scope';
 
 import { AccessTokenScopeList } from './AccessTokenScopeList';
@@ -14,15 +15,59 @@ import { AccessTokenScopeList } from './AccessTokenScopeList';
 type AccessTokenScopeSelectProps = {
   /** React Hook Form's register function for a field named "scopes" */
   register: UseFormRegisterReturn<'scopes'>;
+  watch: Scope[];
 };
 
 /**
  * Displays a list of permissions in a recursive, nested checkbox interface.
  */
-export const AccessTokenScopeSelect: React.FC<AccessTokenScopeSelectProps> = ({ register }) => {
+export const AccessTokenScopeSelect: React.FC<AccessTokenScopeSelectProps> = ({ register, watch }) => {
+  const [disabledScopes, setDisabledScopes] = useState<Set<string>>(new Set());
+  const ScopesMap = parseScopes({ scopes: SCOPE, isAdmin: false });
+
+  const extractScopes = (obj: Record<string, any>): 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<string>();
+
+    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) => {
+          if (s.startsWith(prefix) && s !== scope) {
+            disabledSet.add(s);
+          }
+        });
+      }
+    });
+
+    setDisabledScopes(disabledSet);
+  }, [watch]);
+
   return (
     <div className="border rounded">
-      <AccessTokenScopeList scopeObject={parseScopes({ scopes: SCOPE, isAdmin: true })} register={register} />
+      <AccessTokenScopeList scopeObject={ScopesMap} register={register} disabledScopes={disabledScopes} />
     </div>
   );
 };