Admin Panel

The admin panel is a system for managing various aspects of the application, including blog categories, files, orders, roles, stories, users, and more. It is a combination of server-side rendering and dynamic content management to ensure a seamless experience for administrators. The admin panel is protected by authentication and role-based access control, ensuring that only authorized users can access specific sections.

Features

🔐 Role-Based Access Control: Ensures that only authorized users, such as admins, can access specific sections of the admin panel. This is managed through Supabase and Next.js's server-side capabilities.

📝 Dynamic Content Management: Using React for component-based architecture and Supabase for backend data management, provides forms and pages for creating, editing, and viewing different content types such as blog categories, roles, stories, and more.

Server-Side Rendering: Powered by Next.js's built-in server-side rendering (SSR) features, utilizes server-side rendering to fetch data and render pages dynamically, ensuring that the most up-to-date information is displayed.

🔒 Authentication Protection: Handled through Supabase for authentication and Next.js's middleware for route protection, the admin panel is protected by authentication, ensuring that only logged-in users with the appropriate roles can access it.

🎨 SCSS Styling: The admin panel uses SCSS (Sass) for styling components, providing flexibility and maintainability in the design and appearance of the application.

Screenshots

Admin Panel
Admin Panel
Admin Panel

What's Included

< AdminLayout />

The AdminLayout component serves as the primary layout for the admin panel, including the sidebar and header for navigation. Component redirects users to the home page if they are not authorized admin users.

AdminLayout Props

NameTypeDescription
childrenReactNodeThe main content to be displayed within the layout.

< Sidebar />

The Sidebar component displays the navigation links for different sections of the admin panel, such as blog categories, roles, stories, and more.

< Header />

The Header component displays the admin menu and provides additional context about the current section of the admin panel.

Blog Categories Page

CreateBlogCategoryPage

This page provides a form to create a new blog category. It renders a BlogCategoryForm wrapped in a Page component.

EditBlogCategoryPage

This page provides a form to edit an existing blog category. It fetches the blog category data using the getBlogCategory action and renders the BlogCategoryForm with the edit prop.

EditBlogCategoryPage Props
NameTypeDescription
paramsobjectContains the id of the blog category to be edited.

BlogCategoriesPage

This page lists all blog categories and allows administrators to view them. Fetches all blog categories using the getAllBlogCategories action and renders the BlogCategories component with the list of categories.

Blog Category Components

< BlogCategories />

Displays a list of blog categories and provides actions for editing and deleting categories.

BlogCategories Props
NameTypeDescription
categoriesarrayAn array of blog category data to be displayed in the table.

< BlogCategoriesHeader />

Displays the header for the Blog Categories page, including a button to add a new category if there are existing categories.

BlogCategoriesHeader Props
NameTypeDescription
listbooleanA boolean indicating whether to display the "Add Category" button.

< BlogCategoryForm />

Handles the creation and editing of blog categories.

BlogCategoryForm Props
NameTypeDescription
editbooleanA boolean indicating if the form is in edit mode.
blogCategoryobjectThe blog category data being edited (if in edit mode).

Blog Posts

BlogPostsPage

This page lists all blog posts managed within the system. Fetches all blog posts using the getAllBlogPosts action and renders the BlogPosts component with the list of posts.

Blog Post Components

< BlogPosts />

Displays a list of blog posts and provides actions for editing, deleting, and updating draft and featured statuses.

BlogPosts Props
NameTypeDescription
postsarrayAn array of blog post data to be displayed in the table.

< BlogPostsHeader />

Displays the header for the Blog Posts page, including a button to add a new blog post if there are existing posts.

BlogPostsHeader Props
NameTypeDescription
listbooleanA boolean indicating whether to display the "Add Post" button.

< BlogPostForm />

Handles the creation and editing of blog posts.

BlogPostForm Props
NameTypeDescription
editbooleanA boolean indicating if the form is in edit mode.
blogPostobjectThe blog post data being edited (if in edit mode).
categoriesarrayAn array of available blog categories to select from.
userobjectThe user data associated with the blog post.

Files Page

FilesPage

This page lists all files managed within the system. It renders a list of files with a header containing a title and description.

File Components

< Files />

Manages file uploads and displays uploaded files within tabs categorized by file type (e.g., general files, blog post cover images).

Orders Page

OrdersPage

This page lists all orders managed within the system. Fetches all orders using the getAllOrders action and renders the Orders component with the list of orders.

Roles

CreateRolePage

This page provides a form to create a new role. It renders a RoleForm wrapped in a Page component.

EditRolePage

This page provides a form to edit an existing role. Fetches the role data using the getRole action and renders the RoleForm with the edit prop.

EditRolePage Props

NameTypeDescription
paramsobjectContains the id of the role to be edited.

RolesPage

This page lists all roles managed within the system. Fetches all roles using the getAllRoles action and renders the Roles component with the list of roles.

RolePermissionsPage

This page manages the permissions assigned to a specific role. Fetches the role permissions using the getRolePermissions action and the role details using the getRole action then renders the Permissions component with the edit prop.

RolePermissionsPage Props

NameTypeDescription
paramsobjectContains the id of the role for which permissions are being managed.

Role Components

< Roles />

Displays a list of roles and provides actions for managing permissions, editing, and deleting roles.

Roles Props
NameTypeDescription
rolesarrayAn array of role data to be displayed in the table.

< RolesHeader />

Displays the header for the Roles page, including a button to add a new role if there are existing roles.

RolesHeader Props
NameTypeDescription
listbooleanA boolean indicating whether to display the "Add Role" button.

< RoleForm />

Handles the creation and editing of roles.

RoleForm Props
NameTypeDescription
editbooleanA boolean indicating if the form is in edit mode.
roleobjectThe role data being edited (if in edit mode).

Stories Page

CreateStoryPage

This page provides a form to create a new story. It renders a StoryForm wrapped in a Page component.

EditStoryPage

This page provides a form to edit an existing story. Fetches the story data using the getStory action and renders the StoryForm with the edit prop.

EditStoryPage Props
NameTypeDescription
paramsobjectContains the id of the story to be edited.

StoriesPage

This page lists all stories managed within the system. Fetches all stories using the getAllStories action and renders the Stories component with the list of stories.

Story Components

< Stories />

Displays a list of stories and provides actions for editing and deleting stories.

Stories Props
NameTypeDescription
storiesarrayAn array of story data to be displayed in the table.

< StoriesHeader />

Displays the header for the Stories page, including a button to add a new story if there are existing stories.

StoriesHeader Props
NameTypeDescription
listbooleanA boolean indicating whether to display the "Add Story" button.

< StoryForm />

Handles the creation and editing of stories.

StoryForm Props
NameTypeDescription
editbooleanA boolean indicating if the form is in edit mode.
storyobjectThe story data being edited (if in edit mode).

Users Page

UsersPage

This page lists all users managed within the system. Fetches all users using the getAllUsers action and renders the Users component with the list of users.

User Components

< Users />

Displays a list of users within the system.

Users Props
NameTypeDescription
usersarrayAn array of user data to be displayed in the table.

Admin Pages

AdminPage

The main admin dashboard page, displays a greeting and basic information about the logged-in admin user. Fetches the authenticated user's data using the getSupabaseUser action and displays a personalized greeting based on the time of day and the user's name.

AdminNotFound

The 404 error page for the admin panel. Displays a large "404" message indicating that the requested page was not found.

< ProtectedLayout />

The ProtectedLayout component ensures that only authenticated users can access certain parts of the admin panel. If the user is not authenticated, they are redirected to the sign-in page. Fetches the authenticated user's data using the getSupabaseUser action and redirects the user to the sign-in page if they are not authenticated..

ProtectedLayout Props

NameTypeDescription
childrenReactNodeThe content to be displayed if the user is authenticated.

Setup Component's Needs

To ensure your admin panel functions correctly with Supabase, follow these steps:

  1. Sign Up or Log In to Supabase

  2. Create a New Supabase Project

    • In the Supabase dashboard, create a new project. You'll receive an API URL and an API Key for your project.
  3. Configure Environment Variables

    • In the root of your Next.js project, create or update your .env.local file. Add the following environment variables and replace the placeholders with your actual Supabase details:
 NEXT_PUBLIC_BLOG_COVER_SUPABASE_BUCKET=your-supabase-bucket-for-covers
 NEXT_PUBLIC_BLOG_POSTS_SUPABASE_BUCKET=your-supabase-bucket-for-posts
 NEXT_PUBLIC_FILE_SUPABASE_BUCKET=your-supabase-bucket-for-files
 SUPER_ADMIN_ACCOUNTS=[email protected],[email protected]

4.Set Up Row Level Security (RLS)

  • Use Supabase's Row Level Security (RLS) policies to restrict access to certain data based on user roles. This can be configured directly in the Supabase dashboard under your project's settings.
  • RLS ensures that only users with appropriate roles (like admin) can access or modify specific data.

How to Do Guides

How to Add New CRUD Functionality to the Admin Panel

Step 1: Define the Entity

  • Identify the Entity: Determine the name and structure of the new entity (e.g. Product, Story, BlogCategory).

  • Create Database Schema: Define the database schema for the new entity using Prisma. Add a new model to your schema.prisma file.

Here is an example:

model Story
{
  id         String       @id @unique @default(cuid())
  name       String
  createdAt  DateTime     @default(now())
  updatedAt  DateTime?    @updatedAt
  Permission Permission[]
}

Step 2: Create Actions

Create server-side actions to handle the CRUD operations for the new entity.

  1. Create Action (create<Entity>.js): This action will handle the creation of a new entity.

Example: createStory.js

'use server';
 
import getAllRoles from '@/actions/(authorized)/role/getAllRoles';
import checkActionPermission from '@/actions/checkActionPermission';
 
import prisma from '@/lib/prisma';
 
import { CREATE } from '@/constants/permissions';
import { STORY } from '@/constants/stories';
 
const createStory = async (story) => {
  const p = await checkActionPermission({
    story: STORY,
    permission: CREATE,
  });
 
  if (!hasPermission) return null;
 
  const roles = await getAllRoles();
  const storyData = await prisma.story.create({ data: story });
 
  for (const role of roles) {
    await prisma.permission.create({
      data: {
        roleId: role.id,
        storyId: storyData.id,
      },
    });
  }
 
  return storyData;
};
 
export default createStory;
  1. Read Action (get<Entity>.js):

This action will handle reading a single entity by its ID. Example: getStory.js

'use server';
 
import checkActionPermission from '@/actions/checkActionPermission';
 
import prisma from '@/lib/prisma';
 
import { READ } from '@/constants/permissions';
import { STORY } from '@/constants/stories';
 
const getStory = async (id) => {
  const p = await checkActionPermission({
    story: STORY,
    permission: READ,
  });
 
  if (!hasPermission) return null;
 
  return prisma.story.findUnique({
    where: {
      id: id,
    },
  });
};
 
export default getStory;
  1. Update Action (update<Entity>.js):

This action will handle updating an existing entity. Example: updateStory.js

'use server';
 
import checkActionPermission from '@/actions/checkActionPermission';
 
import prisma from '@/lib/prisma';
 
import { UPDATE } from '@/constants/permissions';
import { STORY } from '@/constants/stories';
 
const updateStory = async (id, story) => {
  const p = await checkActionPermission({
    story: STORY,
    permission: UPDATE,
  });
 
  if (!hasPermission) return null;
 
  return prisma.story.update({
    where: {
      id: id,
    },
    data: story,
  });
};
 
export default updateStory;
  1. Delete Action (delete<Entity>.js):

This action will handle deleting an entity by its ID. Example: deleteStory.js

'use server';
 
import checkActionPermission from '@/actions/checkActionPermission';
 
import prisma from '@/lib/prisma';
 
import { DELETE } from '@/constants/permissions';
import { STORY } from '@/constants/stories';
 
const deleteStory = async (id) => {
  const p = await checkActionPermission({
    story: STORY,
    permission: DELETE,
  });
 
  if (!hasPermission) return null;
 
  return prisma.story.delete({
    where: {
      id: id,
    },
  });
};
 
export default deleteStory;
  1. List Action (getAll<Entity>s.js): This action will handle listing all instances of the entity. Example: getAllStories.js
'use server';
 
import checkActionPermission from '@/actions/checkActionPermission';
 
import prisma from '@/lib/prisma';
 
import { READ } from '@/constants/permissions';
import { STORY } from '@/constants/stories';
 
const getAllStories = async () => {
  const p = await checkActionPermission({
    story: STORY,
    permission: READ,
  });
 
  if (!hasPermission) return null;
 
  return prisma.story.findMany();
};
 
export default getAllStories;

Step 3: Create Components

Create React components for displaying, editing, and managing the new entity within the admin panel.

  • Form Component (<Entity>Form.js): This component handles creating and editing the entity. Example: StoryForm.js

  • List Component (<Entity>s.js): This component lists all instances of the entity and provides actions for editing and deleting. Example: Stories.js

  • Header Component (<Entity>sHeader.js): This component provides a header with actions like adding a new entity. Example: StoriesHeader.js

Step 4: Create Admin Pages

Create the Next.js pages for managing the CRUD operations.

  1. Create Page (Create<Entity>Page.js): Handles the creation of a new entity using the form component. Example: CreateStoryPage.js

  2. Edit Page (Edit<Entity>Page.js): Handles editing an existing entity using the form component. Example: EditStoryPage.js

  3. List Page (<Entity>sPage.js): Displays a list of entities using the list component. Example: StoriesPage.js

Step 5: Update Constants

  • Update your constants to include the new entity. Example: Update Stories (stories.js):
export const STORY = 'story';
  • Update Permissions (permissions.js):
export const PERMISSIONS = {
  [READ]: false,
  [CREATE]: false,
  [UPDATE]: false,
  [DELETE]: false,
  [SELF]: false,
};

Step 6: Update Navigation

Update the admin panel navigation to include the new entity.

  • Update Admin Navigation (adminNav.js): Add a new menu item for the new entity.
{
  name: 'Stories',
  story: STORY,
  icon: <IoDocumentLockOutline style={style} />,
  path: ADMIN_PATHS.stories,
},

Step 7: Seed Data

Create and run seed data scripts to populate the database with initial data for testing.

  • Seed Script (seed.js):

Here is an example for 'Story' entity:

const { PrismaClient } = require('@prisma/client');
 
const prisma = new PrismaClient();
 
async function main() {
  const role = await prisma.story.create({
    data: {
      name: 'role',
    },
  });
  const permission = await prisma.story.create({
    data: {
      name: 'permission',
    },
  });
  const blogCategory = await prisma.story.create({
    data: {
      name: 'blogCategory',
    },
  });
  const blogPost = await prisma.story.create({
    data: {
      name: 'blogPost',
    },
  });
  const user = await prisma.story.create({
    data: {
      name: 'user',
    },
  });
  const order = await prisma.story.create({
    data: {
      name: 'order',
    },
  });
  const file = await prisma.story.create({
    data: {
      name: 'file',
    },
  });
  const product = await prisma.story.create({
    data: {
      name: 'product',
    },
  });
}
 
main()
  .then(async () => {
    await prisma.$disconnect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });