Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
February 21, 2022 08:41 am GMT

Create Full Featured Admin Panel with React and Ant Design

refine is a headless React internal tool framework. It helps you develop quickly while developing both B2B and B2C applications. While speeding you up, it is never restricted and has a fully customizable structure.

Key features

Headless : So you can bring your own UI and fuel it with Refine for top speed development.

Zero-configuration : One-line setup with superplate. It takes less than a minute to start a project.

Out-of-the-box : Routing, networking, authentication, state management, i18n and UI.

Backend Agnostic : Connects to any custom backend. Built-in support for REST API, GraphQL, NestJs CRUD, Airtable, Strapi, Strapi v4, Strapi GraphQL, Supabase, Hasura, Appwrite, Firebase and Altogic.

Native Typescript Core : You can always opt out for plain JavaScript.

Enterprise UI : Works seamlessly with Ant Design System. (Support for multiple UI frameworks is on the Roadmap)

Boilerplate-free Code : Keeps your codebase clean and readable.

You can use it with any UI library you want without any problems. Also, Ant Design as out-of-the-box is supported.

refine directly provides Ant Design components and some hooks to work with those components. These hooks give you the required props for those Ant Design components.

Refine Advanced Tutorial

This article will proceed through refine's Refine Basic Tutorial. That's why I suggest you read the basic tutorial of refine.

In this tutorial, we will learn how to include the features(i18n, Realtime, Access Control) provided by the refine in our project and how we can use it.

Create Refine Project

Let's start by creating our refine project. You can use the superplate to create a refine project.

npx superplate-cli -p refine-react refine-advanced-tutorial
 What will be the name of your app  refine-advanced-tutorial Package manager:  npm Do you want to using UI Framework?:  antd Do you want to customize theme?:  css Data Provider:  custom-json-rest-data-provider Auth Provider:  none Do you want to add an example page?  example-resource Do you want to customize layout?  no

Do you want to add an example page? example-resource By selecting you can view the tutorial in your local.

cd refine-advanced-tutorialnpm run dev

refine_tutorial

As you have seen, our example project is ready. Now let's examine how the features offered by refine are included in a project and how they are used.

Adding i18n Provider to Your Project

Create i18n Instance

First, we will create an i18n instance using react-i18next.

src/i18n.ts:

import i18n from "i18next";import { initReactI18next } from "react-i18next";import Backend from "i18next-xhr-backend";import detector from "i18next-browser-languagedetector";i18n  .use(Backend)  .use(detector)  .use(initReactI18next)  .init({    supportedLngs: ["en", "de"],    backend: {      loadPath: "/locales/{{lng}}/{{ns}}.json",    },    defaultNS: "common",    fallbackLng: ["en", "de"],  });export default i18n;

Let's import the i18n instance we created in index.tsx. Then let's wrap the App in React.Suspense.

src/index.tsx:

import React from "react";import ReactDOM from "react-dom";import App from "./App";import "./i18n";ReactDOM.render(    <React.StrictMode>        <React.Suspense fallback="loading">            <App />        </React.Suspense>    </React.StrictMode>,    document.getElementById("root"),);

Let's define our i18n provider and give it a refine.

src/App.tsx:

import { Refine } from "@pankod/refine-core";import { notificationProvider, Layout } from "@pankod/refine-antd";import routerProvider from "@pankod/refine-react-router";import "@pankod/refine-antd/dist/styles.min.css";import dataProvider from "@pankod/refine-simple-rest";import { PostList, PostCreate, PostEdit, PostShow } from "pages/posts";import { useTranslation } from "react-i18next";function App() {  const { t, i18n } = useTranslation();  const i18nProvider = {    translate: (key: string, params: object) => t(key, params),    changeLocale: (lang: string) => i18n.changeLanguage(lang),    getLocale: () => i18n.language,  };  return (    <Refine      routerProvider={routerProvider}      notificationProvider={notificationProvider}      Layout={Layout}      dataProvider={dataProvider("https://api.fake-rest.refine.dev")}      resources={[        {          name: "posts",          list: PostList,          create: PostCreate,          edit: PostEdit,          show: PostShow,        },      ]}      i18nProvider={i18nProvider}    />  );}export default App;

Now let's add our own translation. Let's create two separate json files in English and German.

/public/locales/en/common.json

{  "posts": {    "posts": "Posts",    "fields": {      "id": "Id",      "title": "Title",      "category": "Category",      "status": {        "title": "Status",        "published": "Published",        "draft": "Draft",        "rejected": "Rejected"      },      "content": "Content",      "createdAt": "Created At"    },    "titles": {      "create": "Create Post",      "edit": "Edit Post",      "list": "Posts",      "show": "Show Post"    }  },  "table": {    "actions": "Actions"  }}

/public/locales/de/common.json

{  "posts": {    "posts": "Eintrge",    "fields": {      "id": "Id",      "title": "Titel",      "category": "Kategorie",      "status": {        "title": "Status",        "published": "Verffentlicht",        "draft": "Draft",        "rejected": "Abgelehnt"      },      "content": "Inhalh",      "createdAt": "Erstellt am"    },    "titles": {      "create": "Erstellen",      "edit": "Bearbeiten",      "list": "Eintrge",      "show": "Eintrag zeigen"    }  },  "table": {    "actions": "Aktionen"  }}

In this article, we have included the translation of only a small part as an example.

Now, let's create a select component in the header and examine our posts according to the language we have chosen.

src/components/header.tsx:

import { useGetLocale, useSetLocale } from "@pankod/refine-core";import {  AntdLayout,  Space,  Menu,  Button,  Icons,  Dropdown,} from "@pankod/refine-antd";import { useTranslation } from "react-i18next";const { DownOutlined } = Icons;export const Header: React.FC = () => {  const { i18n } = useTranslation();  const locale = useGetLocale();  const changeLanguage = useSetLocale();  const currentLocale = locale();  const menu = (    <Menu selectedKeys={[currentLocale]}>      {[...(i18n.languages || [])].sort().map((lang: string) => (        <Menu.Item key={lang} onClick={() => changeLanguage(lang)}>          {lang === "en" ? "English" : "German"}        </Menu.Item>      ))}    </Menu>  );  return (    <AntdLayout.Header      style={{        display: "flex",        justifyContent: "flex-end",        alignItems: "center",        padding: "0px 24px",        height: "48px",        backgroundColor: "#FFF",      }}    >      <Dropdown overlay={menu}>        <Button type="link">          <Space>            {currentLocale === "en" ? "English" : "German"}            <DownOutlined />          </Space>        </Button>      </Dropdown>    </AntdLayout.Header>  );};

Let's define the header we created within the refine.

  return (    <Refine      routerProvider={routerProvider}      notificationProvider={notificationProvider}      Layout={Layout}      i18nProvider={i18nProvider}      dataProvider={dataProvider("https://api.fake-rest.refine.dev")}      Header={Header}      resources={[        {          name: "posts",          list: PostList,          create: PostCreate,          edit: PostEdit,          show: PostShow,        },      ]}    />  );

Now our i18n Provider is ready to use, let's test it together.

i18n Provider

Use the translation with Table Content

import {   useTranslate,  useMany,} from "@pankod/refine-core";import {  List,  Table,  TextField,  useTable,  Space,  EditButton,  ShowButton,} from "@pankod/refine-antd";import { IPost, ICategory } from "interfaces";export const PostList: React.FC = () => {  const translate = useTranslate();  const { tableProps } = useTable<IPost>();  const categoryIds =      tableProps?.dataSource?.map((item) => item.category.id) ?? [];  const { data, isLoading } = useMany<ICategory>({      resource: "categories",      ids: categoryIds,      queryOptions: {          enabled: categoryIds.length > 0,      },  });  return (      <List>          <Table {...tableProps} rowKey="id">              <Table.Column dataIndex="id" title="ID" />              <Table.Column                  dataIndex="title"                  title={translate("posts.fields.title")}              />              <Table.Column                  dataIndex={["category", "id"]}                  title={translate("posts.fields.category")}                  render={(value) => {                      if (isLoading) {                          return <TextField value="Loading..." />;                      }                      return (                          <TextField                              value={                                  data?.data.find((item) => item.id === value)                                      ?.title                              }                          />                      );                  }}              />              <Table.Column<IPost>                  title={translate("table.actions")}                  dataIndex="actions"                  key="actions"                  render={(_value, record) => (                      <Space>                          <EditButton size="small" recordItemId={record.id} />                          <ShowButton size="small" recordItemId={record.id} />                      </Space>                  )}              />          </Table>      </List>  );};

Image description

You can add the translation you want and organize your content according to different languages together with the refine i18n Provider.

Check out refine i18n Provider for more detailed information and step-by-step guide

Add Live Provider(Realtime) to Your Project with Refine

refine lets you add Realtime support to your app via liveProvider prop for . It can be used to update and show data in Realtime throughout your app.

Now let's make our application Realtime using the refine Live Provider

We will using Ably in this article to provide Realtime features.

Installation

We need to install Ably live provider package from refine.

 npm install @pankod/refine-ably

First, let's create ably-client and define our Ably API key.

src/utility/client.ts:

import { Ably } from "@pankod/refine-ably";export const ablyClient = new Ably.Realtime("YOUR_ABLY_API_KEY");

Then pass liveProvider from @pankod/refine-ably to .

src/App.tsx:

import { Refine } from "@pankod/refine-core";import { notificationProvider, Layout } from "@pankod/refine-antd";import routerProvider from "@pankod/refine-react-router";import "@pankod/refine-antd/dist/styles.min.css";import dataProvider from "@pankod/refine-simple-rest";import { liveProvider } from "@pankod/refine-ably";import { ablyClient } from "utility";import { PostList, PostCreate, PostEdit, PostShow } from "pages/posts";import { Header } from "./components/header";import { useTranslation } from "react-i18next";function App() {  const { t, i18n } = useTranslation();  const i18nProvider = {    translate: (key: string, params: object) => t(key, params),    changeLocale: (lang: string) => i18n.changeLanguage(lang),    getLocale: () => i18n.language,  };  return (    <Refine      routerProvider={routerProvider}      notificationProvider={notificationProvider}      Layout={Layout}      i18nProvider={i18nProvider}      dataProvider={dataProvider("https://api.fake-rest.refine.dev")}      Header={Header}      liveProvider={liveProvider(ablyClient)}      liveMode="auto"      resources={[        {          name: "posts",          list: PostList,          create: PostCreate,          edit: PostEdit,          show: PostShow,        },      ]}    />  );}export default App;

You can configure liveMode, this example use "auto" mode.

Check out Refine Live Provider for more detailed information and step-by-step guide.

Our project is now Realtime! Thanks to refine Live Provider, we made our project Realtime by adding only 2 lines.

Let's see how our RealTime Project works.

Refine Live Provider

Add Access Control to Your Project with Refine

You can control your project as you wish with the refine react admin framework. Now let's add Access Control Provider to our refine project.

Access control is a broad topic where there are lots of advanced solutions that provide different set of features. refine is deliberately agnostic for its own API to be able to integrate different methods (RBAC, ABAC, ACL, etc.) and different libraries (Casbin, CASL, Cerbos, AccessControl.js). can method would be the entry point for those solutions.

Refer to the Access Control Provider documentation for detailed information.

Let's create two Roles, Admin and Editor. Admin have full CRUD authority on the posts. The Editor role, on the other hand, only has the authority to create and edit new posts. In other words, the person in the editor role cannot delete the posts and cannot view all the rows on the table.

Let's start by creating two buttons for the Admin and Editor roles in our created Header Component.

/src/componets/header.tsx:

import { useGetLocale, useSetLocale } from "@pankod/refine-core";import {  AntdLayout,  Space,  Menu,  Button,  Icons,  Dropdown,  Radio,} from "@pankod/refine-antd";import { useTranslation } from "react-i18next";const { DownOutlined } = Icons;interface HeaderProps {  role: string;}export const Header: React.FC<HeaderProps> = ({ role }) => {  const { i18n } = useTranslation();  const locale = useGetLocale();  const changeLanguage = useSetLocale();  const currentLocale = locale();  const menu = (    <Menu selectedKeys={[currentLocale]}>      {[...(i18n.languages || [])].sort().map((lang: string) => (        <Menu.Item key={lang} onClick={() => changeLanguage(lang)}>          {lang === "en" ? "English" : "German"}        </Menu.Item>      ))}    </Menu>  );  return (    <AntdLayout.Header      style={{        display: "flex",        justifyContent: "space-between",        alignItems: "center",        padding: "0px 24px",        height: "48px",        backgroundColor: "#FFF",      }}    >      <Radio.Group      value={role}      onChange={(event) => {        localStorage.setItem("role", event.target.value);        location.reload();      }}      >        <Radio.Button value="admin">Admin</Radio.Button>        <Radio.Button value="editor">Editor</Radio.Button>      </Radio.Group>      <Dropdown overlay={menu}>        <Button type="link">          <Space>            {currentLocale === "en" ? "English" : "German"}            <DownOutlined />          </Space>        </Button>      </Dropdown>    </AntdLayout.Header>  );};

Refine Role Button

In this article, we will use Cerbos to refine Access Control.

npm install cerbos

After the installation is complete, let's create a Cerbos object in the App.tsx file and define it in .

import { Cerbos } from "cerbos";const cerbos = new Cerbos({  hostname: "https://demo-pdp.cerbos.cloud", // The Cerbos PDP instance  playgroundInstance: "WS961950bd85QNYlAvTmJYubP0bqF7e3", // The playground instance ID to test});
    <Refine      routerProvider={routerProvider}      notificationProvider={notificationProvider}      Layout={Layout}      i18nProvider={i18nProvider}      dataProvider={dataProvider("https://api.fake-rest.refine.dev")}      Header={() => <Header role={role} />}      liveProvider={liveProvider(ablyClient)}      liveMode="auto"      accessControlProvider={{        can: async ({ action, params, resource }) => {          const cerbosPayload = {            principal: {              id: "demoUser", // Fake a user ID              roles: [role],              // this is where user attributes can be passed              attr: {},            },            // the resouces being access - can be multiple            resource: {              kind: resource,              instances: {                [params?.id || "new"]: {                  attr: params,                },              },            },            // the list of actions on the resource to check authorization for            actions: [action],          };          const result = await cerbos.check(cerbosPayload);          return Promise.resolve({            can: result.isAuthorized(params?.id || "new", action),          });        },      }}      resources={[        {          name: "posts",          list: PostList,          create: PostCreate,          edit: PostEdit,          show: PostShow,          canDelete: true,        },      ]}    />

We will perform our actions according to the role we choose from the header. As you can see above, we set this with the access Control Provider can method.

Now using the refine useCanhook Let's perform operations according to roles within our list.

src/pages/PostList.tsx:

import {  IResourceComponentsProps,  useMany,  useTranslate,  useCan,} from "@pankod/refine-core";import {  List,  Table,  TextField,  useTable,  Space,  EditButton,  ShowButton,  FilterDropdown,  useSelect,  Select,  Radio,  TagField,  NumberField,} from "@pankod/refine-antd";import { IPost, ICategory } from "interfaces";export const PostList: React.FC<IResourceComponentsProps> = () => {  const translate = useTranslate();  const { tableProps } = useTable<IPost>();  const categoryIds =    tableProps?.dataSource?.map((item) => item.category.id) ?? [];  const { data, isLoading } = useMany<ICategory>({    resource: "categories",    ids: categoryIds,    queryOptions: {      enabled: categoryIds.length > 0,    },  });  const { selectProps: categorySelectProps } = useSelect<ICategory>({    resource: "categories",    optionLabel: "title",    optionValue: "id",  });  const { data: canAccess } = useCan({    resource: "posts",    action: "field",    params: { field: "hit" },  });  return (    <List>      <Table {...tableProps} rowKey="id">        <Table.Column dataIndex="id" title="ID" />        <Table.Column          dataIndex="title"          title={translate("posts.fields.title")}        />        <Table.Column          dataIndex={["category", "id"]}          title={translate("posts.fields.category")}          render={(value) => {            if (isLoading) {              return <TextField value="Loading..." />;            }            return (              <TextField                value={data?.data.find((item) => item.id === value)?.title}              />            );          }}          filterDropdown={(props) => (            <FilterDropdown {...props}>              <Select                style={{ minWidth: 200 }}                mode="multiple"                placeholder="Select Category"                {...categorySelectProps}              />            </FilterDropdown>          )}        />        {canAccess?.can && (          <Table.Column            dataIndex="hit"            title="Hit"            render={(value: number) => (              <NumberField                value={value}                options={{                  notation: "compact",                }}              />            )}          />        )}        <Table.Column          dataIndex="status"          title="Status"          render={(value: string) => <TagField value={value} />}          filterDropdown={(props: any) => (            <FilterDropdown {...props}>              <Radio.Group>                <Radio value="published">Published</Radio>                <Radio value="draft">Draft</Radio>                <Radio value="rejected">Rejected</Radio>              </Radio.Group>            </FilterDropdown>          )}        />        <Table.Column<IPost>          title={translate("table.actions")}          dataIndex="actions"          render={(_, record) => (            <Space>              <EditButton hideText size="small" recordItemId={record.id} />              <ShowButton hideText size="small" recordItemId={record.id} />            </Space>          )}        />      </Table>    </List>  );};

Here, if the selected role is Admin, the 'Hit' section will appear in our Table. We have stated that the Editor role cannot display this section.

Refine Access Control

Check out refine Access Control Provider for more detailed information and step-by-step guide

Conclusion

In this tutorial, we have shown that the features of the refine internal tool Framework are useful and how simple it is. These features will shorten your development time considerably. While Refine offers you the opportunity to develop quickly, it does not limit you and gives you the opportunity to customize your project as you wish.

We saw how simple it was for refine to include and use the Internationalization (i18n), Live Provider(Realtime) and Access Control features. You can develop more complex applications in a simple way with refine.

With refine react admin, you can develop any web application you want with Admin Panel, Basic Crud App or Next.js-SSR Support.

refine offers the opportunity to develop B2B and B2C applications without any restrictions and in a fully customizable manner.

Check out for detailed information about refine.

For information about other features of refine

Live CodeSandbox Example


Original Link: https://dev.to/pankod/create-full-featured-admin-panel-with-react-and-ant-design-3po8

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To