Build a React admin dashboard with Supabase integration

With the fast pace of development in the current age, it is essential to create a strong React admin dashboard with speed and efficiency. This tutorial will lead you through building a robust React admin dashboard by merging Supabase’s powerful backend capabilities with the flexibility of a react admin dashboard template.

Supabase provides several easy-to-use features like user authentication, database management, and storage. At the same time, React Admin offers an adjustable and customizable admin interface for modern web applications. 

Looking for a modern UI built without the React Admin library?

Explore the react admin dashboard template built with React JS and Shadcn UI Pills of Zen offers a clean, flexible design to kickstart your admin panel.

Key Features of Your React Admin Panel Example

  • Authentication with Supabase Auth using React admin
  • Data management with Supabase Database in a React admin dashboard
  • Image upload and storage with Supabase Storage
  • User following the system
  • Post management system
  • Modern UI with Material-UI as part of your React admin panel example

What Is Supabase?  A Backend-as-a-Service for React Apps

Supabase is an open-source backend-as-a-service for React apps. It provides a complete backend solution that includes a PostgreSQL database, user authentication, file storage, and real-time functionality. It also serves as an open-source alternative to Firebase.

In this project, Supabase handles all backend tasks like authenticating users, storing posts and images, managing user relationships, and sending real-time updates. This makes it perfect for developing a secure Supabase admin panel integrated with a React frontend.

Zen-style React admin dashboard template

What Is React Admin and Why It’s Ideal for Admin Dashboards?

React Admin is a powerful framework used for developing admin dashboards and panels for React applications. It offers pre-existing hooks and components for standard operations like data views, form handling, and user authentication.

In our case, React Admin allows us to build a complete React CRUD admin panel efficiently. With its reusable UI components, we’ll create and manage posts, update profiles, and upload media. It also assists us in maintaining our user experience consistent and professional.

Prerequisites for Building Your React Admin Dashboard Tutorial

Before diving into the implementation, ensure you have the following:

  • Node.js (version 14 or higher)
  • npm or yarn package manager
  • A Supabase account (sign up at https://supabase.com)
  • Basic understanding of React and TypeScript
  • A code editor (VS Code recommended)

You’ll also benefit from basic familiarity with Supabase integration with React and how to build a React admin dashboard tutorial using real-time backend services.

Set Up of Your Supabase Project for React Integration

Step 1: Set Up Your Supabase Project

  1. Visit https://supabase.com and click “Start your project.”
  2. Create a new account
  3. Click the “New Project” button
  4. Fill in the project details

This project setup showcases how Supabase can serve as a backend-as-a-service for react apps, handling everything from user authentication to real-time data updates efficiently.

Step 2: Configure Project Settings

  1. Wait for the project to be created (usually takes 1-2 minutes)
  2. Once ready, you’ll be redirected to the project dashboard
  3. Save your project credentials:
    – Project URL (found in Project Settings > API)
    – Project API Key (anon/public key)

Step 3: Create Tables (Database)

We’ll build a small but functional backend schema for our React admin dashboard:

  1. Go to your Supabase project dashboard
  2. Click on “SQL Editor” in the left sidebar
  3. Click “New Query.”
  4. Paste the following code
  • To create a posts table
create table posts (
  id uuid primary key default gen_random_uuid(),
  title text,
  content text,
  user_id uuid references auth.users(id) on delete cascade,
  created_at timestamp default now(),
  image_url text
);Code language: JavaScript (javascript)
  • To create the user_followers table
create table user_followers (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) on delete cascade,
  following_id uuid references auth.users(id) on delete cascade,
  created_at timestamp default now()
);
create unique index unique_follow on user_followers(user_id, following_id);Code language: JavaScript (javascript)
  • To create a profiles table,
create table public.profiles (
 id uuid primary key references auth.users(id) on delete cascade,
 email text, 
 created_at timestamp default now()
);
create function public.handle_new_user()
returns trigger as $$
begin
  insert into public.profiles (id, email)
  values (new.id, new.email);
  return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();Code language: JavaScript (javascript)
  1. Click “Run” to execute the query.

Step 4: Enable Required Services

1. Create authentication users by following the steps below:

  • Click on “Authentication” in the left sidebar
  • Click on “Users” under the “MANAGE” menu.
  • Click the “Add user” dropdown button on the top right and select the “Create new user” option
  • Fill in the required fields (such as Email, Password, etc.) in the dialog that appears and click on the “Create user” button
  • Repeat the same process for creating multiple users.

2. Create a storage bucket by following the steps below:

  • Click on “Storage” in the left sidebar
  • Click the “New bucket” button at the top left.
  • In the dialog that appears, enter “uploads” as the bucket name.
  • Click “Save” to finish.

This will support file uploads in your React forms with Supabase data.

Step 5: Secure the Table by RLS

  • Set Row-Level Security (RLS) rules in Supabase to protect data by enabling RLS for posts and user_followers tables and setting policies.

Step 1: Enable RLS for the posts Table

  • Go to the Table Editor in your Supabase project.
  • Select the posts table
  • Click the “RLS Disabled” button to enable Row-Level Security. This will activate RLS on the table.

Step 2: Add Your First Policy – Allow Users to Insert Their Own Posts

  • Once RLS is enabled, click the “Add RLS Policy” button.
  • You’ll be taken to a page that lists all RLS policies for the posts table. Since no policies exist yet, the list will be empty.
  • Click Create Policy.
  • A side panel will appear where you can define your policy.
  • Select the INSERT operation and choose the “Enable insert for users based on user_id” template.
  • You can customize the policy if needed or stick with the default.
  • Click “Save Policy”.
  • Follow the above Step 1 and Step 2 for enabling RLS and adding an insert policy for the user_followers table.
  • Follow the above Step 1 for enabling RLS for the profiles table.
  • Add a policy in the user_followers table – Enable delete for users based on user_id. This policy will allow users to delete or unfollow users
    • Click “Add RLS Policy” again
    • Select the DELETE operation
    • Choose the Enable delete for users based on user_idtemplate.
    • Click “Save Policy.”
  • Add a policy in the profiles table – Enable read access for all users with the Authenticated role.
    • Click “Add RLS Policy” again
    • Select the SELECT operation
    • Choose the “Enable read access for all users with Authenticated role” template.
    • Click “Save Policy.”
  • Add a second policy in the user_followers table – Enable users to view their data only
    • Click “Add RLS Policy” again.
    • Choose the “SELECT” operation.
    • Pick the “Enable users to view their own data only” template.
    • Click “Save Policy.”
  • Now, add a second policy in the posts table – Allow users to read posts created by themselves or users they follow
    • Go to the SQL Editor
    • Copy and paste the following code in the SQL editor to create an RLS policy for the posts table.
create policy "Allow reading own and followed users' posts"
on posts for select
using (
  auth.uid() = user_id OR
  exists (
    select 1 from user_followers
    where user_id = auth.uid()
    and following_id = posts.user_id
  )
);Code language: JavaScript (javascript)
  • Click on the “Run” button
  • Your new policy will be created for the posts table. Now go to the policies page to verify all the policies.
  • Add the last policy to the uploads storage bucket – Only authenticated users can fetch and upload into the  storage bucket
    • Go to Storage
    • Click on “Policies” under configuration
    • Click on “New Policy” for the uploads storage bucket
    • Click  on “Get started quickly” to create a policy from a template
    • Now select “Give users access to a folder only to authenticated users” template
    • Click on the “Use this template” button
    • Click on the “Select” and “Insert” checkboxes in Allowed Operations
    • Click on the “Review” button
    • Click on the “Save Policy” Button
    • Similarly, add policies for “Other policies under storage.objects” and “Policies under storage.buckets” options as per the image below.

       Supabase storage policy setup for React apps

Set Up Your React Admin Project with Supabase

Creating a feature-rich React admin dashboard with Supabase backend begins with setting up your React project and connecting it to your Supabase instance. Let’s walk through the steps.

Before we dive into the setup, explore this free, beautifully crafted react admin template – Pills of Zen that we’ll be using as the base layout for building our dashboard.

Step 1: Create a new React Admin project

To get started with the React admin dashboard template, run the following command:

npm create react-admin@latest my-admin-dashboard
cd my-admin-dashboardCode language: CSS (css)

This sets up the base structure for your admin dashboard using React.

Step 2: Install necessary dependencies

Install the required packages to enable Supabase integration with React:

npm install @supabase/supabase-js ra-supabaseCode language: CSS (css)

These packages form the core of your Supabase admin panel, including authentication, storage, and data management.

Step 3: Create a ‘.env’ file in the root directory with your Supabase credentials

Grab the key and URL from your Supabase dashboard under Project Settings > Data API.

VITE_SUPABASE_URL=your_supabase_url
VITE_SUPABASE_ANON_KEY=your_supabase_anon_key

This is critical for secure Supabase authentication in your React app.

Step 4: Code Implementation

To start building with the React Admin app and Supabase, we use the `AdminGuesser` component from the ra-supabase package, providing us with an easy means of generating a fully working admin interface with minimal code.

This utility generates automatically enhanced admin views for all of your Supabase tables. You can later customize these automatically generated views to your liking as your project grows by replacing them with your own List, Edit, Create, and Show components by simply updating the Resource components in your App.tsx file.

src/App.tsx

import { AdminGuesser } from "ra-supabase";
export const App = () => {
  const instanceUrl = import.meta.env.VITE_SUPABASE_URL || "";
  const apiKey = import.meta.env.VITE_SUPABASE_ANON_KEY || "";

  return <AdminGuesser instanceUrl={instanceUrl} apiKey={apiKey} />;
};Code language: JavaScript (javascript)

Open your browser console (F12 or right-click → Inspect → Console) to inspect the component structure and data flow, and then customize the auto-generated components or configure custom components to improve your admin dashboard’s interface and functionality.

See the following screenshot to see how the code and component hierarchy look in the browser’s console.

// browser’s console 

React admin dashboard code structure preview

Before starting customization, explore the react admin UI components live demo to see how Pills of Zen implements a unique Shadcn UI-based design.

Customizing Your Application:

Once your basic React admin dashboard is scaffolded, the next step is customizing your admin panel using modular components, providers, and resource bindings.

Supabase Client Setup

Configures Supabase connection using environment variables to provide security.

src/supabaseClient.ts

import { createClient } from "@supabase/supabase-js";

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl, supabaseAnonKey);Code language: JavaScript (javascript)

React Admin Data Provider

It automatically integrates with Supabase and React Admin for handling all data operations such as creating, reading, updating, and deleting records.

src/App.tsx 

const instanceUrl = import.meta.env.VITE_SUPABASE_URL;
const apiKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
const supabaseClient = createClient(instanceUrl, apiKey);


const dataProvider = supabaseDataProvider({
  instanceUrl,
  apiKey,
  supabaseClient,
});Code language: JavaScript (javascript)

Authentication Provider

The authentication provider is an important aspect that is responsible for managing all operations related to authentication, including the management of user logins, logouts, and session management, with inherent security checks and error handling.

For more clarity on setting up supabase authentication react, refer to our full integration guide.

src/App.tsx 

const authProvider = supabaseAuthProvider(supabaseClient, {
  getIdentity: async () => {
    const {
      data: { user },
    } = await supabaseClient.auth.getUser();
    if (!user) {
      throw new Error("User not found");
    }
    return {
      id: user.id,
      fullName: user.email || ""
    };
  },
});Code language: JavaScript (javascript)

Integration with React Admin

The auth provider and data provider are integrated into the main app component with resources. Resources in React Admin represent your data models and their operations. Each resource is associated with a table in your database.

src/App.tsx 

import { Admin } from "react-admin";
 
<Admin
      dataProvider={dataProvider}
      authProvider={authProvider}
      // ... other props
    >
        {/* ... resources */}
</Admin>Code language: JavaScript (javascript)

Create your App.tsx component to bring everything together:

import { Admin, Resource, CustomRoutes } from "react-admin";
import { BrowserRouter, Route } from "react-router-dom";
import { createClient } from "@supabase/supabase-js";
import {
  ForgotPasswordPage,
  LoginPage,
  SetPasswordPage,
  defaultI18nProvider,
  supabaseDataProvider,
  supabaseAuthProvider,
} from "ra-supabase";
import { PostList } from "./components/posts/PostList";
import { PostCreate } from "./components/posts/PostCreate";
import { PostEdit } from "./components/posts/PostEdit";
import { ProfileList } from "./components/profiles/ProfileList";
import { ProfileShow } from "./components/profiles/ProfileShow";

const instanceUrl = import.meta.env.VITE_SUPABASE_URL;
const apiKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
const supabaseClient = createClient(instanceUrl, apiKey);
// Configure the data provider with proper options
const dataProvider = supabaseDataProvider({
  instanceUrl,
  apiKey,
  supabaseClient,
});

// Configure the auth provider with proper options
const authProvider = supabaseAuthProvider(supabaseClient, {
  getIdentity: async () => {
    const {
      data: { user },
    } = await supabaseClient.auth.getUser();
    if (!user) {
      throw new Error("User not found");
    }
    return {
      id: user.id,
      fullName: user.email || "",
    };
  },
});

export const App = () => (
  <BrowserRouter>
    <Admin
      dataProvider={dataProvider}
      authProvider={authProvider}
      i18nProvider={defaultI18nProvider}
      loginPage={LoginPage}
      requireAuth
    >
      <Resource
        name="posts"
        list={PostList}
        edit={PostEdit}
        create={PostCreate}
      />
      <Resource name="profiles" list={ProfileList} show={ProfileShow} />


      <CustomRoutes noLayout>
        <Route path={SetPasswordPage.path} element={<SetPasswordPage />} />
        <Route
          path={ForgotPasswordPage.path}
          element={<ForgotPasswordPage />}
        />
      </CustomRoutes>
    </Admin>
  </BrowserRouter>
);Code language: JavaScript (javascript)

Post Components

Post components have a powerful content management system based on the React Admin framework with a safe CRUD interface and user permissions, image management through Supabase storage, and necessary post metadata management. The system architecture is based on modular development with separate components for listing (`PostList`), creation (`PostCreate`), editing (`PostEdit`), and image upload (`ImageUploadField`), with a good level of data validation, secure file management, and a simple-to-use interface for administration of content.

Post List Component

This displays a posts table with title, content, created date, and image columns, all coming from React-Admin’s Datagrid component.

It includes a custom DeletePostButton that only appears for the post owner and handles both post deletion and associated image removal from Supabase storage. The list also features filtering capabilities for title, content, creation date, and uses optimistic UI updates with success/error notifications for delete operations.

src/components/posts/PostList.tsx

import {
  Datagrid,
  DateField,
  DateInput,
  ImageField,
  List,
  TextField,
  TextInput,
  DeleteButton,
  useRecordContext,
  useGetIdentity,
  useNotify,
} from "react-admin";
import { supabase } from "../../supabaseClient";


interface Post {
  id: string;
  title: string;
  content: string;
  user_id: string;
  created_at: string;
  image_url?: string;
}

const DeletePostButton = () => {
  const record = useRecordContext<Post>();
  const { identity } = useGetIdentity();
  const notify = useNotify();

  if (!record) return null;

  const handleDelete = async () => {
    try {
      if (record.image_url) {
        const imagePath = record.image_url.split("/").pop();
        if (imagePath) {
          await supabase.storage
            .from("uploads")
            .remove([`public/${imagePath}`]);
        }
      }

      await supabase.from("posts").delete().eq("id", record.id);

      notify("Post deleted successfully", { type: "success" });
    } catch (error) {
      console.error("Error in delete operation:", error);
      notify("Failed to delete post", { type: "error" });
      throw error;
    }
  };

  if (!identity || record.user_id !== identity.id) {
    return null;
  }

  return <DeleteButton mutationMode="pessimistic" onClick={handleDelete} />;
};

const filters = [
  <TextInput source="title" />,
  <TextInput source="content" />,
  <DateInput source="created_at" />,
];

export const PostList = () => (
  <List filters={filters}>
    <Datagrid bulkActionButtons={false}>
      <TextField source="title" />
      <TextField source="content" />
      <DateField source="created_at" />
      <ImageField source="image_url" />
      <DeletePostButton />
    </Datagrid>
  </List>
);Code language: JavaScript (javascript)

Post Create Component 

The field has a neatly structured layout that is made to be expandable to fit new posts, with a clear space for content and title both necessities and an optional space for inserting images. Under the hood, it quietly appends the logged user ID and creation date as hidden fields, courtesy of React-Admin’s SimpleForm component.

src/components/posts/PostCreate.tsx

import { Create, DateInput, SimpleForm, TextInput, useGetIdentity } from 'react-admin';
import { ImageUploadField } from './ImageUploadField';
           
export const PostCreate = () => {
    const { identity } = useGetIdentity();

    return (
        <Create>
            <SimpleForm
                defaultValues={{
                    user_id: identity?.id,
                    created_at: new Date().toISOString()
                }}
            >
                <TextInput source="title" required/>
                <TextInput source="content" multiline rows={4} required/>
                 <TextInput
                    source="user_id"
                    style={{ display: 'none' }}
                />
                <DateInput
                    source="created_at"
                    style={{ display: 'none' }}
                />
                <ImageUploadField source="image_url" />
            </SimpleForm>
        </Create>
    );
};Code language: JavaScript (javascript)

Post Edit Component 

Here, users are given a form view where they are able to edit their own posts, changing the title, body, and image of the post. The interface makes use of React-Admin’s `SimpleForm` component with unlabeled fields for user_id and created_at to keep data intact and an image upload field where the post image can be changed.

src/components/posts/PostEdit.tsx

import { DateInput, Edit, SimpleForm, TextInput } from "react-admin";
import { ImageUploadField } from "./ImageUploadField";
export const PostEdit = () => (
  <Edit>
    <SimpleForm>
      <TextInput source="title" />
      <TextInput source="content" />
      <TextInput source="user_id" style={{ display: "none" }} />
      <DateInput source="created_at" style={{ display: "none" }} />
      <ImageUploadField source="image_url" />
    </SimpleForm>
  </Edit>
);Code language: JavaScript (javascript)

Image Upload Component 

The package is entirely integrated with React-Admin’s code for forms, working in tandem with Supabase storage to provide a complete image upload solution. The image is uploaded into the “uploads” bucket, and the URL is stored in the post “image_url” field, to be displayed in the list view.

src/components/posts/ImageUploadField.tsx

import { useCallback, useState } from "react";
import { useRecordContext, useInput, useGetIdentity } from "react-admin";
import { Box, Button, Typography } from "@mui/material";
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
import { supabase } from "../../supabaseClient";

export const ImageUploadField = (props: { source: string }) => {
  const { source } = props;
  const record = useRecordContext();
  const { field } = useInput(props);
  const { identity } = useGetIdentity();
  const [uploading, setUploading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [uploadedUrl, setUploadedUrl] = useState<string | null>(null);

  const handleFileUpload = useCallback(
    async (event: React.ChangeEvent<HTMLInputElement>) => {
      try {
        setUploading(true);
        setError(null);
        const file = event.target.files?.[0];
        if (!file) return;

        if (!identity?.id) {
          throw new Error("User not authenticated");
        }

        const fileExt = file.name.split(".").pop();
        const fileName = `${Date.now()}-${Math.random().toString(36).substring(2)}.${fileExt}`;
        const filePath = `public/${fileName}`;

        const { error: uploadError, data } = await supabase.storage
          .from("uploads")
          .upload(filePath, file, {
            upsert: true,
            cacheControl: "3600",
          });

        if (uploadError) {
          throw uploadError;
        }

        // Get signed URL (valid for 30 days)
        const { data: signedUrlData } = await supabase.storage
          .from("uploads")
          .createSignedUrl(data.path, 60 * 60 * 24 * 30);


        if (!signedUrlData) {
          throw new Error("Failed to get signed URL");
        }

        field.onChange(signedUrlData.signedUrl);
        setUploadedUrl(signedUrlData.signedUrl);
      } catch (error) {
        setError(
          error instanceof Error ? error.message : "Failed to upload image",
        );
      } finally {
        setUploading(false);
      }
    },
    [field, identity],
  );

  const imageUrl = uploadedUrl || record?.[source] || field.value;

  return (
    <Box>
      <input
        accept="image/*"
        style={{ display: "none" }}
        id="image-upload"
        type="file"
        onChange={handleFileUpload}
        disabled={uploading}
      />
      <label htmlFor="image-upload">
        <Button
          variant="contained"
          component="span"
          startIcon={<CloudUploadIcon />}
          disabled={uploading}
        >
          {uploading ? "Uploading..." : "Upload Image"}
        </Button>
      </label>
      {error && (
        <Typography color="error" sx={{ mt: 1 }}>
          {error}
        </Typography>
      )}
      {imageUrl && (
        <Box mt={2}>
          <img
            src={imageUrl}
            alt="Uploaded"
            style={{
              maxWidth: "200px",
              maxHeight: "200px",
              objectFit: "contain",
              border: "1px solid #ddd",
              borderRadius: "4px",
              padding: "4px",
            }}
            onError={(e) => {
              setError("Failed to load image");
              e.currentTarget.style.display = "none";
            }}
          />
        </Box>
      )}
    </Box>
  );
};Code language: JavaScript (javascript)

Profile Components

The profile section has an advanced user profile system with social network features, where Profile List (`ProfileList`) has a complete list of all users by email and date created, and Profile Show (`ProfileShow`) has complete user data and follow/unfollow interactive buttons, all handled by Supabase’s user_followers table with real-time status and adequate security checks to avoid self-following.

Profile List Component

This returns a Datagrid of user profiles with their email and date created using React-Admin’s Datagrid component. It has a custom FollowStatusField to display whether the current user follows each profile with live status updates and follow/unfollow functionality using Supabase.

src/components/profiles/ProfileList.tsx

import {
  Datagrid,
  DateField,
  EmailField,
  List,
  useGetIdentity,
  useRecordContext,
} from "react-admin";
import { useState, useEffect } from "react";
import { supabase } from "../../supabaseClient";

const FollowStatusField = () => {
  const record = useRecordContext();
  const { identity } = useGetIdentity();
  const [isFollowing, setIsFollowing] = useState(false);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (record?.id && identity?.id) {
      checkFollowStatus(String(record.id));
    }
  }, [record?.id, identity?.id]);

  const checkFollowStatus = async (profileId: string) => {
    try {
      const { data, error } = await supabase
        .from("user_followers")
        .select("*")
        .eq("user_id", identity?.id)
        .eq("following_id", profileId)
        .single();

      if (error) throw error;
      setIsFollowing(!!data);
    } catch (error) {
      console.error("Error checking follow status:", error);
    } finally {
      setLoading(false);
    }
  };

  if (!identity || !record?.id || identity.id === record.id) {
    return null;
  }

  return loading ? "Loading..." : isFollowing ? "Following" : "Not Following";
};

export const ProfileList = () => (
  <List>
    <Datagrid bulkActionButtons={false}>
      <EmailField source="email" />
      <DateField source="created_at" />
      <FollowStatusField />
    </Datagrid>
  </List>
);Code language: JavaScript (javascript)

Profile Show Component

This field offers detailed information about a particular user profile, such as their email and creation date, through React-Admin’s SimpleShowLayout.

It includes a dedicated `FollowButton` component where users can easily follow or unfollow the profile. With live status updates and adequate error handling via Supabase’s user_followers table, it provides a seamless experience.

src/components/profiles/ProfileShow.tsx

import {
  DateField,
  EmailField,
  Show,
  SimpleShowLayout,
  useGetIdentity,
  useRecordContext,
  Button,
} from "react-admin";
import { useState, useEffect } from "react";
import { supabase } from "../../supabaseClient";

const FollowButton = () => {
  const record = useRecordContext();
  const { identity } = useGetIdentity();
  const [isFollowing, setIsFollowing] = useState(false);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (record?.id && identity?.id) {
      checkFollowStatus(String(record.id));
    }
  }, [record?.id, identity?.id]);

  const checkFollowStatus = async (profileId: string) => {
    try {
      const { data, error } = await supabase
        .from("user_followers")
        .select("*")
        .eq("user_id", identity?.id)
        .eq("following_id", profileId)
        .single();

      if (error) throw error;
      setIsFollowing(!!data);
    } catch (error) {
      console.error("Error checking follow status:", error);
    } finally {
      setLoading(false);
    }
  };

  const handleFollow = async (profileId: string) => {
    try {
      if (isFollowing) {
        // Unfollow
        const { error } = await supabase
          .from("user_followers")
          .delete()
          .eq("user_id", identity?.id)
          .eq("following_id", profileId);

        if (error) throw error;
        setIsFollowing(false);
      } else {
        const { error } = await supabase.from("user_followers").insert([
          {
            user_id: identity?.id,
            following_id: profileId,
          },
        ]);


        if (error) throw error;
        setIsFollowing(true);
      }
    } catch (error) {
      console.error("Error toggling follow status:", error);
    }
  };

  if (!identity || !record?.id || identity.id === record.id) {
    return null;
  }

  return (
    <Button
      label={isFollowing ? "Unfollow" : "Follow"}
      onClick={() => handleFollow(String(record.id))}
      disabled={loading}
      color="primary"
      variant={isFollowing ? "outlined" : "contained"}
    />
  );
};

export const ProfileShow = () => (
  <Show>
    <SimpleShowLayout>
      <EmailField source="email" />
      <DateField source="created_at" />
      <FollowButton />
    </SimpleShowLayout>
  </Show>
);Code language: JavaScript (javascript)

Step 5: Start the application

Now that everything is in place, it is time to launch your app.

On your terminal, type the following command –

npm run dev

The development server is now running, and you can access the Supabase-powered React Admin application by navigating to http://localhost:5173 in your web browser.

To start using the application, please log in with the authentication users you created in Supabase.

After successful authentication, you can begin creating posts by submitting the post creation form with title, content, and optionally uploading a photo.

To follow a user, go to the Profiles page, where you can see all the users. Click on any user’s profile to go to their full view, where you will have a ‘Follow’ button.

Final Thoughts on Building a React Admin Dashboard with Supabase

Integration of Supabase with React Admin creates a solid full-stack solution by combining the simple admin UI of React Admin with the robust backend service capabilities of Supabase. This kind of synergistic Supabase integration brings about effortless authentication, real-time data sync, and secure file storage capability from the beginning.

This leads to an out-of-the-box production-grade admin dashboard that can be easily deployed and customized to meet certain business requirements, especially for companies looking to build a React admin dashboard backed by a scalable, open-source backend-as-a-service.

If you’re looking to extend functionality to mobile, consider partnering with a react native app development company to create a unified web and mobile admin interface.You can explore the complete project setup, including Supabase authentication in React, row-level security policies, Supabase CRUD operations, and a clean React admin panel example, in the GitHub repository.

Build your next big idea with a scalable tech stack

Author's Bio

Asmita Shenvi - React and Supabase tutorial author
Asmita Shenvi

Senior Software Engineer at Mobisoft Infotech with 5.8 years of experience in frontend development. I focus on building scalable web applications using React.js, Vue.js, and Next.js frameworks, specializing in implementing complex state management solutions and optimizing performance for seamless user experiences across all devices. My expertise spans from responsive UI development and server-side rendering to progressive web apps, enabling the delivery of solutions that drive business growth while staying current with emerging frontend technologies and best practices.