pankod/refine

View on GitHub
packages/core/src/components/canAccess/index.tsx

Summary

Maintainability
A
1 hr
Test Coverage
import React, { useEffect } from "react";
import { UseQueryOptions } from "@tanstack/react-query";

import { useCan, useResourceParams } from "@hooks";

import { CanReturnType } from "../../contexts/accessControl/types";
import { BaseKey } from "../../contexts/data/types";
import { IResourceItem, ITreeMenu } from "../../contexts/resource/types";

type CanParams = {
  resource?: IResourceItem & { children?: ITreeMenu[] };
  id?: BaseKey;
  [key: string]: any;
};

type OnUnauthorizedProps = {
  resource?: string;
  reason?: string;
  action: string;
  params: CanParams;
};

type CanAccessBaseProps = {
  /**
   * Resource name for API data interactions
   */
  resource?: string;
  /**
   * Intended action on resource
   */
  action: string;
  /**
   * Parameters associated with the resource
   * @type { resource?: [IResourceItem](https://refine.dev/docs/api-reference/core/interfaceReferences/#canparams), id?: [BaseKey](https://refine.dev/docs/api-reference/core/interfaceReferences/#basekey), [key: string]: any }
   */
  params?: CanParams;
  /**
   * Content to show if access control returns `false`
   */
  fallback?: React.ReactNode;
  /**
   * Callback function to be called if access control returns `can: false`
   */
  onUnauthorized?: (props: OnUnauthorizedProps) => void;
  children: React.ReactNode;
  queryOptions?: UseQueryOptions<CanReturnType>;
};

type CanAccessWithoutParamsProps = {
  [key in Exclude<
    keyof CanAccessBaseProps,
    "fallback" | "children"
  >]?: undefined;
} & {
  [key in "fallback" | "children"]?: CanAccessBaseProps[key];
};

export type CanAccessProps = CanAccessBaseProps | CanAccessWithoutParamsProps;

export const CanAccess: React.FC<CanAccessProps> = ({
  resource: resourceFromProp,
  action: actionFromProp,
  params: paramsFromProp,
  fallback,
  onUnauthorized,
  children,
  queryOptions: componentQueryOptions,
  ...rest
}) => {
  const {
    id,
    resource,
    action: fallbackAction = "",
  } = useResourceParams({
    resource: resourceFromProp,
    id: paramsFromProp?.id,
  });

  const action = actionFromProp ?? fallbackAction;

  const params = paramsFromProp ?? {
    id,
    resource,
  };

  const { data } = useCan({
    resource: resource?.name,
    action,
    params,
    queryOptions: componentQueryOptions,
  });

  useEffect(() => {
    if (onUnauthorized && data?.can === false) {
      onUnauthorized({
        resource: resource?.name,
        action,
        reason: data?.reason,
        params,
      });
    }
  }, [data?.can]);

  if (data?.can) {
    if (React.isValidElement(children)) {
      const Children = React.cloneElement(children, rest);
      return Children;
    }

    return <>{children}</>;
  }

  if (data?.can === false) {
    return <>{fallback ?? null}</>;
  }

  return null;
};