0.3.0 • Published 1 year ago

@macellan/dux-core v0.3.0

Weekly downloads
-
License
MIT
Repository
-
Last release
1 year ago

Introduction

Dux is a simple boilerplate reducing tamplate for creating dashboards written in TypeScript and React. While the Dux Core doesn't have much dependencies, Dux Components package heavily relies on Mantine as a UI kit and hook library of choice. Dux is unopinonated about your state management of choice.

Installation

To get started with Dux, install the core and components package to your existing app or create a template with Codux tool (coming soon).

Using npm:

npm install @macellan/dux-core @macellan/dux-components

Using yarn:

yarn add @macellan/dux-core @macellan/dux-components

Using pnpm:

pnpm install @macellan/dux-core @macellan/dux-components

Usage

To start using the Dux Core you should create a new dux instance, best place to this would be a config file or your main.tsx file where you initialize your react app.

This documentation will examplify the code in TypeScript but things shouldn't change much for JavaScript.

import { createDux } from '@macellan/dux-core';

const app = createDux();

Dux expects some essential options for creating an instance, like your views. Under the hood Dux uses react-router to implement client side routing. You can pass a router object to the views property as a router. This object can be extended with allowedRoles propery to do authenticated routing. Dux provides 3 builtin roles to give you a headstart and handle internal mechanisms. These are;

  • * Anyone can access this route.
  • authenticated Only users that are authenticated with given Dux primitive can access this route.
  • unauthenticated Only users that are unauthenticated with given Dux primitive can access this route. This is useful for creating a login or signup page that only the users that are not logged in can see.
import { createDux } from '@macellan/dux-core';

const app = createDux({
  views: [
    {
      path: '/',
      element: <div>This is the home page</div>,
      // Anyone can see this page
      allowedRoles: ['*'],
    },
    {
      path: '/dashboard',
      element: <div>This is the dashboard page</div>,
      // Only logged in users can see this page
      allowedRoles: ['authenticated'],
    },
    {
      path: '/admin',
      element: <div>This is the admin page</div>,
      // You can define your arbitrary roles as well
      // Only logged in users that has the admin role can see this page
      allowedRoles: ['authenticated', 'admin'],
    },
    {
      path: '/login',
      element: <div>This is the login page</div>,
      // Only logged out users can see this page
      allowedRoles: ['unauthenticated'],
    },
  ],
});

allowedRoles property only works at the root level so you have to manage your routes accordingly.

Another neccessary property a Dux instance needs is the initial authentication state. Implementing the authentication is on you but we're planning a @macellan/dux-auth package to speed up this process as well.

import { createDux } from '@macellan/dux-core';

const app = createDux({
  views: [
    {
      path: '/',
      element: <div>This is the home page</div>,
      // Anyone can see this page
      allowedRoles: ['*'],
    },
    {
      path: '/dashboard',
      element: <div>This is the dashboard page</div>,
      // Only logged in users can see this page
      allowedRoles: ['authenticated'],
    },
    {
      path: '/admin',
      element: <div>This is the admin page</div>,
      // You can define your arbitrary roles as well
      // Only logged in users that has the admin role can see this page
      allowedRoles: ['authenticated', 'admin'],
    },
    {
      path: '/login',
      element: <div>This is the login page</div>,
      // Only logged out users can see this page
      allowedRoles: ['unauthenticated'],
    },
  ],
  auth: {
    // For kepping track on if the user is logged in. Implement your own logic here
    initialState: false,
    // You should fetch your user before reaching here and provide your roles
    initialRoles: [],
  },
});

initialRoles accepts a RoleObject array, this is a simple object with a name and an identifier properties. You can provide your arbitrary roles here.

export interface RoleObject {
  identifier: string;
  name: string;
}

With that, you are ready to render your app. Simply create your app provider and render it with react.

// This is an async funtion that you need to wait
app.createProvider().then(DuxProvider => {
  ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
    <React.StrictMode>
      <DuxProvider />
    </React.StrictMode>
  );
});

Authorization

At some point you may need an authorization manager. For instance you might want to redirect unauthroized pages or simply show an "unauthorized" view. app instance provides a method to manage your views. You basically provide a function that would be called when Dux encounters an unauthorized view visit.

You get the view and the roles the user has in this callback function. You need to provide an object that has 2 properties;

  • redirects for redirection
  • replaces for displaying a custom view

You should pass null for at least one of these properties but redirection will take precedence otherwise.

app.configureAuthorizationManager((view: ViewObject, roles: RoleObject[]) => {
  // Let's redirect the client if the user is 'unauthenticated' 
  if (roles.map(role => role.identifier).includes('unauthenticated')) {
    return {
      redirects: "/login",
      replaces: null,
    };
  }

  return {
    redirects: null,
    replaces: null,
  };
})

We might want to show an "unauthorized" view for other pages.

app.configureAuthorizationManager((view: ViewObject, roles: RoleObject[]) => {
  if (roles.map(role => role.identifier).includes('unauthenticated')) {
    return {
      redirects: "/login",
      replaces: null,
    };
  }

  // Let's first exlude views like login and signup
  const authRedirectRoutes = ["/login", "/signup"]
  if (authRedirectRoutes.includes(view.path as string)) {
    return {
      redirects: "/",
      replaces: null,
    };
  }

  // Otherwise show the unauthorized view
  return {
    redirects: null,
    replaces: () => <div>You are not authorized to see this page</div>,
  };
});

Dux provides a simple factory for this simple usage. Simply pass your primary authentication page, your non authenticated view paths, default redirect and unauthorized view to this function and you're good to go.

import { createDux, defaultAuthorizationManager } from "@macellan/dux-core"

// ...

app.configureAuthorizationManager(
  defaultAuthorizationManager('/login', ['/login', '/signup'], '/', () => <div>You are not authorized to see this page</div>)
);

Authentication

Authentication is quite simple with Dux. Simply use the useAuth hook and login and logout your user.

Dux Core does NOT handle the storage or management of the user, that is on you. Dux simply governs the auth state and the roles.

function Login() {
  // use your app instance here
  const { login } = app.useAuth()

  const handleLogin = () => {
    // Provide your users roles as a RoleObject array. 
    // You can pass an empty array for basic users
    login([{ identifier: "admin", name: "Admin" }]);
  }

  return // ...
}
function UserMenu() {
  // use your app instance here
  const { logout } = app.useAuth()

  const handleLogout = () => {
    logout();
  }

  return // ...
}

These functions will automatically redirect the user.