teableio/teable

View on GitHub
apps/nextjs-app/src/features/app/components/oauth/OAuthScope.tsx

Summary

Maintainability
A
1 hr
Test Coverage
import type { Action } from '@teable/core';
import { ActionPrefix } from '@teable/core';
import { Hash, PackageCheck, Sheet, Square, Table2, User } from '@teable/icons';
import { usePermissionActionsStatic } from '@teable/sdk/hooks';
import { Badge, cn } from '@teable/ui-lib/shadcn';
import type { ReactNode } from 'react';
import { useMemo } from 'react';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const IconMap: Partial<Record<ActionPrefix, React.JSXElementConstructor<any>>> = {
  [ActionPrefix.Table]: Table2,
  [ActionPrefix.Field]: Hash,
  [ActionPrefix.Record]: Square,
  [ActionPrefix.View]: Sheet,
  [ActionPrefix.Automation]: PackageCheck,
  [ActionPrefix.User]: User,
};

export const OAuthScope = (props: {
  scopes?: string[];
  description?: string | ReactNode;
  className?: string;
}) => {
  const { scopes, description, className } = props;
  const { actionPrefixStaticMap, actionStaticMap } = usePermissionActionsStatic();

  const scopeMap = useMemo(
    () =>
      (scopes || []).reduce(
        (acc, scope) => {
          if (!actionStaticMap) {
            return acc;
          }
          const prefix = scope.split('|')[0] as ActionPrefix;
          const scopeDesc = actionStaticMap[scope as Action].description;
          if (acc[prefix]) {
            acc[prefix].push(scopeDesc);
          } else {
            acc[prefix] = [scopeDesc];
          }
          return acc;
        },
        {} as Record<ActionPrefix, string[]>
      ),
    [actionStaticMap, scopes]
  );
  return (
    <div className={cn('space-y-3 px-8', className)}>
      {description && typeof description === 'string' ? (
        <div className="text-center">{description}</div>
      ) : (
        description
      )}
      {Object.entries(scopeMap).map(([prefix, scopes]) => {
        const ScopeIcon = IconMap[prefix as ActionPrefix];
        return (
          <div key={prefix} className="space-y-2">
            <strong className="flex items-center gap-2 text-sm">
              {ScopeIcon && <ScopeIcon />}
              {actionPrefixStaticMap[prefix as ActionPrefix].title}
            </strong>
            <div className="flex flex-wrap gap-2">
              {scopes.map((scope) => (
                <Badge key={scope} variant={'outline'} className="text-xs font-normal">
                  {scope}
                </Badge>
              ))}
            </div>
          </div>
        );
      })}
    </div>
  );
};